@digitalculture/ochre-sdk 0.11.19 → 0.11.21
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/index.d.ts +1185 -0
- package/dist/index.js +3119 -0
- package/package.json +1 -2
package/dist/index.js
ADDED
|
@@ -0,0 +1,3119 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/schemas.ts
|
|
4
|
+
/**
|
|
5
|
+
* Schema for validating UUIDs
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
const uuidSchema = z.uuid({ error: "Invalid UUID provided" });
|
|
9
|
+
/**
|
|
10
|
+
* Schema for validating website properties
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
const websiteSchema = z.object({
|
|
14
|
+
type: z.enum([
|
|
15
|
+
"traditional",
|
|
16
|
+
"digital-collection",
|
|
17
|
+
"plum",
|
|
18
|
+
"cedar",
|
|
19
|
+
"elm",
|
|
20
|
+
"maple",
|
|
21
|
+
"oak",
|
|
22
|
+
"palm"
|
|
23
|
+
], { error: "Invalid website type" }),
|
|
24
|
+
status: z.enum([
|
|
25
|
+
"development",
|
|
26
|
+
"preview",
|
|
27
|
+
"production"
|
|
28
|
+
], { error: "Invalid website status" }),
|
|
29
|
+
privacy: z.enum([
|
|
30
|
+
"public",
|
|
31
|
+
"password",
|
|
32
|
+
"private"
|
|
33
|
+
], { error: "Invalid website privacy" })
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* Valid component types for web elements
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
const componentSchema = z.enum([
|
|
40
|
+
"annotated-document",
|
|
41
|
+
"annotated-image",
|
|
42
|
+
"audio-player",
|
|
43
|
+
"bibliography",
|
|
44
|
+
"button",
|
|
45
|
+
"collection",
|
|
46
|
+
"empty-space",
|
|
47
|
+
"entries",
|
|
48
|
+
"iframe",
|
|
49
|
+
"iiif-viewer",
|
|
50
|
+
"image",
|
|
51
|
+
"image-gallery",
|
|
52
|
+
"map",
|
|
53
|
+
"network-graph",
|
|
54
|
+
"query",
|
|
55
|
+
"search-bar",
|
|
56
|
+
"table",
|
|
57
|
+
"text",
|
|
58
|
+
"timeline",
|
|
59
|
+
"video"
|
|
60
|
+
], { error: "Invalid component" });
|
|
61
|
+
/**
|
|
62
|
+
* Schema for validating data categories
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
const categorySchema = z.enum([
|
|
66
|
+
"resource",
|
|
67
|
+
"spatialUnit",
|
|
68
|
+
"concept",
|
|
69
|
+
"period",
|
|
70
|
+
"bibliography",
|
|
71
|
+
"person",
|
|
72
|
+
"propertyValue",
|
|
73
|
+
"set",
|
|
74
|
+
"tree"
|
|
75
|
+
]);
|
|
76
|
+
/**
|
|
77
|
+
* Schema for validating property value content types
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
const propertyValueContentTypeSchema = z.enum([
|
|
81
|
+
"string",
|
|
82
|
+
"integer",
|
|
83
|
+
"decimal",
|
|
84
|
+
"boolean",
|
|
85
|
+
"date",
|
|
86
|
+
"dateTime",
|
|
87
|
+
"time",
|
|
88
|
+
"coordinate",
|
|
89
|
+
"IDREF"
|
|
90
|
+
]);
|
|
91
|
+
/**
|
|
92
|
+
* Schema for validating gallery parameters
|
|
93
|
+
* @internal
|
|
94
|
+
*/
|
|
95
|
+
const gallerySchema = z.object({
|
|
96
|
+
uuid: z.uuid({ error: "Invalid UUID" }),
|
|
97
|
+
filter: z.string().optional(),
|
|
98
|
+
page: z.number().positive({ error: "Page must be positive" }),
|
|
99
|
+
perPage: z.number().positive({ error: "Per page must be positive" })
|
|
100
|
+
}).strict();
|
|
101
|
+
/**
|
|
102
|
+
* Schema for validating and parsing render options
|
|
103
|
+
* @internal
|
|
104
|
+
*/
|
|
105
|
+
const renderOptionsSchema = z.string().transform((str) => str.split(" ")).pipe(z.array(z.enum([
|
|
106
|
+
"bold",
|
|
107
|
+
"italic",
|
|
108
|
+
"underline"
|
|
109
|
+
])));
|
|
110
|
+
/**
|
|
111
|
+
* Schema for validating and parsing whitespace options
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
114
|
+
const whitespaceSchema = z.string().transform((str) => str.split(" ")).pipe(z.array(z.enum([
|
|
115
|
+
"newline",
|
|
116
|
+
"trailing",
|
|
117
|
+
"leading"
|
|
118
|
+
])));
|
|
119
|
+
/**
|
|
120
|
+
* Schema for validating email addresses
|
|
121
|
+
* @internal
|
|
122
|
+
*/
|
|
123
|
+
const emailSchema = z.email({ error: "Invalid email" });
|
|
124
|
+
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/utils/getters.ts
|
|
127
|
+
const DEFAULT_OPTIONS = { includeNestedProperties: false };
|
|
128
|
+
/**
|
|
129
|
+
* Finds a property by its UUID in an array of properties
|
|
130
|
+
*
|
|
131
|
+
* @param properties - Array of properties to search through
|
|
132
|
+
* @param uuid - The UUID to search for
|
|
133
|
+
* @param options - Search options, including whether to include nested properties
|
|
134
|
+
* @returns The matching Property object, or null if not found
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* const property = getPropertyByUuid(properties, "123e4567-e89b-12d3-a456-426614174000", { includeNestedProperties: true });
|
|
139
|
+
* if (property) {
|
|
140
|
+
* console.log(property.values);
|
|
141
|
+
* }
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
function getPropertyByUuid(properties, uuid, options = DEFAULT_OPTIONS) {
|
|
145
|
+
const { includeNestedProperties } = options;
|
|
146
|
+
const property = properties.find((property$1) => property$1.uuid === uuid);
|
|
147
|
+
if (property) return property;
|
|
148
|
+
if (includeNestedProperties) {
|
|
149
|
+
for (const property$1 of properties) if (property$1.properties.length > 0) {
|
|
150
|
+
const nestedResult = getPropertyByUuid(property$1.properties, uuid, { includeNestedProperties });
|
|
151
|
+
if (nestedResult) return nestedResult;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Retrieves all values for a property with the given UUID
|
|
158
|
+
*
|
|
159
|
+
* @param properties - Array of properties to search through
|
|
160
|
+
* @param uuid - The UUID to search for
|
|
161
|
+
* @param options - Search options, including whether to include nested properties
|
|
162
|
+
* @returns Array of property values as strings, or null if property not found
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* const values = getPropertyValuesByUuid(properties, "123e4567-e89b-12d3-a456-426614174000");
|
|
167
|
+
* if (values) {
|
|
168
|
+
* for (const value of values) {
|
|
169
|
+
* console.log(value);
|
|
170
|
+
* }
|
|
171
|
+
* }
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
function getPropertyValuesByUuid(properties, uuid, options = DEFAULT_OPTIONS) {
|
|
175
|
+
const { includeNestedProperties } = options;
|
|
176
|
+
const property = properties.find((property$1) => property$1.uuid === uuid);
|
|
177
|
+
if (property) return property.values.map((value) => value.content);
|
|
178
|
+
if (includeNestedProperties) {
|
|
179
|
+
for (const property$1 of properties) if (property$1.properties.length > 0) {
|
|
180
|
+
const nestedResult = getPropertyValuesByUuid(property$1.properties, uuid, { includeNestedProperties });
|
|
181
|
+
if (nestedResult) return nestedResult;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Gets the first value of a property with the given UUID
|
|
188
|
+
*
|
|
189
|
+
* @param properties - Array of properties to search through
|
|
190
|
+
* @param uuid - The UUID to search for
|
|
191
|
+
* @param options - Search options, including whether to include nested properties
|
|
192
|
+
* @returns The first property value as string, or null if property not found
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* const title = getPropertyValueByUuid(properties, "123e4567-e89b-12d3-a456-426614174000");
|
|
197
|
+
* if (title) {
|
|
198
|
+
* console.log(`Document title: ${title}`);
|
|
199
|
+
* }
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
function getPropertyValueByUuid(properties, uuid, options = DEFAULT_OPTIONS) {
|
|
203
|
+
const { includeNestedProperties } = options;
|
|
204
|
+
const values = getPropertyValuesByUuid(properties, uuid, { includeNestedProperties });
|
|
205
|
+
if (values !== null && values.length > 0) return values[0];
|
|
206
|
+
if (includeNestedProperties) {
|
|
207
|
+
for (const property of properties) if (property.properties.length > 0) {
|
|
208
|
+
const nestedResult = getPropertyValueByUuid(property.properties, uuid, { includeNestedProperties });
|
|
209
|
+
if (nestedResult !== null) return nestedResult;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Finds a property by its label in an array of properties
|
|
216
|
+
*
|
|
217
|
+
* @param properties - Array of properties to search through
|
|
218
|
+
* @param label - The label to search for
|
|
219
|
+
* @param options - Search options, including whether to include nested properties
|
|
220
|
+
* @returns The matching Property object, or null if not found
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* const property = getPropertyByLabel(properties, "author", { includeNestedProperties: true });
|
|
225
|
+
* if (property) {
|
|
226
|
+
* console.log(property.values);
|
|
227
|
+
* }
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
function getPropertyByLabel(properties, label, options = DEFAULT_OPTIONS) {
|
|
231
|
+
const { includeNestedProperties } = options;
|
|
232
|
+
const property = properties.find((property$1) => property$1.label === label);
|
|
233
|
+
if (property) return property;
|
|
234
|
+
if (includeNestedProperties) {
|
|
235
|
+
for (const property$1 of properties) if (property$1.properties.length > 0) {
|
|
236
|
+
const nestedResult = getPropertyByLabel(property$1.properties, label, { includeNestedProperties });
|
|
237
|
+
if (nestedResult) return nestedResult;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Retrieves all values for a property with the given label
|
|
244
|
+
*
|
|
245
|
+
* @param properties - Array of properties to search through
|
|
246
|
+
* @param label - The label to search for
|
|
247
|
+
* @param options - Search options, including whether to include nested properties
|
|
248
|
+
* @returns Array of property values as strings, or null if property not found
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* const values = getPropertyValuesByLabel(properties, "keywords");
|
|
253
|
+
* if (values) {
|
|
254
|
+
* for (const value of values) {
|
|
255
|
+
* console.log(value);
|
|
256
|
+
* }
|
|
257
|
+
* }
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
function getPropertyValuesByLabel(properties, label, options = DEFAULT_OPTIONS) {
|
|
261
|
+
const { includeNestedProperties } = options;
|
|
262
|
+
const property = properties.find((property$1) => property$1.label === label);
|
|
263
|
+
if (property) return property.values.map((value) => value.content);
|
|
264
|
+
if (includeNestedProperties) {
|
|
265
|
+
for (const property$1 of properties) if (property$1.properties.length > 0) {
|
|
266
|
+
const nestedResult = getPropertyValuesByLabel(property$1.properties, label, { includeNestedProperties });
|
|
267
|
+
if (nestedResult) return nestedResult;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Gets the first value of a property with the given label
|
|
274
|
+
*
|
|
275
|
+
* @param properties - Array of properties to search through
|
|
276
|
+
* @param label - The label to search for
|
|
277
|
+
* @param options - Search options, including whether to include nested properties
|
|
278
|
+
* @returns The first property value as string, or null if property not found
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```ts
|
|
282
|
+
* const title = getPropertyValueByLabel(properties, "title");
|
|
283
|
+
* if (title) {
|
|
284
|
+
* console.log(`Document title: ${title}`);
|
|
285
|
+
* }
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
function getPropertyValueByLabel(properties, label, options = DEFAULT_OPTIONS) {
|
|
289
|
+
const { includeNestedProperties } = options;
|
|
290
|
+
const values = getPropertyValuesByLabel(properties, label, { includeNestedProperties });
|
|
291
|
+
if (values !== null && values.length > 0) return values[0];
|
|
292
|
+
if (includeNestedProperties) {
|
|
293
|
+
for (const property of properties) if (property.properties.length > 0) {
|
|
294
|
+
const nestedResult = getPropertyValueByLabel(property.properties, label, { includeNestedProperties });
|
|
295
|
+
if (nestedResult !== null) return nestedResult;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Gets all unique properties from an array of properties
|
|
302
|
+
*
|
|
303
|
+
* @param properties - Array of properties to get unique properties from
|
|
304
|
+
* @param options - Search options, including whether to include nested properties
|
|
305
|
+
* @returns Array of unique properties
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```ts
|
|
309
|
+
* const properties = getAllUniqueProperties(properties, { includeNestedProperties: true });
|
|
310
|
+
* console.log(`Available properties: ${properties.map((p) => p.label).join(", ")}`);
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
function getUniqueProperties(properties, options = DEFAULT_OPTIONS) {
|
|
314
|
+
const { includeNestedProperties } = options;
|
|
315
|
+
const uniqueProperties = new Array();
|
|
316
|
+
for (const property of properties) {
|
|
317
|
+
if (uniqueProperties.some((p) => p.uuid === property.uuid)) continue;
|
|
318
|
+
uniqueProperties.push(property);
|
|
319
|
+
if (property.properties.length > 0 && includeNestedProperties) {
|
|
320
|
+
const nestedProperties = getUniqueProperties(property.properties, { includeNestedProperties: true });
|
|
321
|
+
for (const property$1 of nestedProperties) {
|
|
322
|
+
if (uniqueProperties.some((p) => p.uuid === property$1.uuid)) continue;
|
|
323
|
+
uniqueProperties.push(property$1);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return uniqueProperties;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Gets all unique property labels from an array of properties
|
|
331
|
+
*
|
|
332
|
+
* @param properties - Array of properties to get unique property labels from
|
|
333
|
+
* @param options - Search options, including whether to include nested properties
|
|
334
|
+
* @returns Array of unique property labels
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```ts
|
|
338
|
+
* const properties = getAllUniquePropertyLabels(properties, { includeNestedProperties: true });
|
|
339
|
+
* console.log(`Available properties: ${properties.join(", ")}`);
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
function getUniquePropertyLabels(properties, options = DEFAULT_OPTIONS) {
|
|
343
|
+
const { includeNestedProperties } = options;
|
|
344
|
+
const uniquePropertyLabels = new Array();
|
|
345
|
+
for (const property of properties) {
|
|
346
|
+
if (uniquePropertyLabels.includes(property.label)) continue;
|
|
347
|
+
uniquePropertyLabels.push(property.label);
|
|
348
|
+
if (property.properties.length > 0 && includeNestedProperties) {
|
|
349
|
+
const nestedProperties = getUniquePropertyLabels(property.properties, { includeNestedProperties: true });
|
|
350
|
+
for (const property$1 of nestedProperties) {
|
|
351
|
+
if (uniquePropertyLabels.includes(property$1)) continue;
|
|
352
|
+
uniquePropertyLabels.push(property$1);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return uniquePropertyLabels;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Filters a property based on a label and value criteria
|
|
360
|
+
*
|
|
361
|
+
* @param property - The property to filter
|
|
362
|
+
* @param filter - Filter criteria containing label and value to match
|
|
363
|
+
* @param filter.label - The label to filter by
|
|
364
|
+
* @param filter.value - The value to filter by
|
|
365
|
+
* @param options - Search options, including whether to include nested properties
|
|
366
|
+
* @returns True if the property matches the filter criteria, false otherwise
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* ```ts
|
|
370
|
+
* const matches = filterProperties(property, {
|
|
371
|
+
* label: "category",
|
|
372
|
+
* value: "book"
|
|
373
|
+
* });
|
|
374
|
+
* if (matches) {
|
|
375
|
+
* console.log("Property matches filter criteria");
|
|
376
|
+
* }
|
|
377
|
+
* ```
|
|
378
|
+
*/
|
|
379
|
+
function filterProperties(property, filter, options = DEFAULT_OPTIONS) {
|
|
380
|
+
const { includeNestedProperties } = options;
|
|
381
|
+
if (filter.label.toLocaleLowerCase("en-US") === "all fields" || property.label.toLocaleLowerCase("en-US") === filter.label.toLocaleLowerCase("en-US")) {
|
|
382
|
+
let isFound = property.values.some((value) => {
|
|
383
|
+
if (value.content === null) return false;
|
|
384
|
+
if (typeof value.content === "string") {
|
|
385
|
+
if (typeof filter.value !== "string") return false;
|
|
386
|
+
return value.content.toLocaleLowerCase("en-US").includes(filter.value.toLocaleLowerCase("en-US"));
|
|
387
|
+
}
|
|
388
|
+
if (typeof value.content === "number") {
|
|
389
|
+
if (typeof filter.value !== "number") return false;
|
|
390
|
+
return value.content === filter.value;
|
|
391
|
+
}
|
|
392
|
+
if (typeof value.content === "boolean") {
|
|
393
|
+
if (typeof filter.value !== "boolean") return false;
|
|
394
|
+
return value.content === filter.value;
|
|
395
|
+
}
|
|
396
|
+
return false;
|
|
397
|
+
});
|
|
398
|
+
if (!isFound && includeNestedProperties) isFound = property.properties.some((property$1) => filterProperties(property$1, filter, { includeNestedProperties: true }));
|
|
399
|
+
return isFound;
|
|
400
|
+
}
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region src/utils/string.ts
|
|
406
|
+
const PRESENTATION_ITEM_UUID = "f1c131b6-1498-48a4-95bf-a9edae9fd518";
|
|
407
|
+
const TEXT_ANNOTATION_UUID = "b9ca2732-78f4-416e-b77f-dae7647e68a9";
|
|
408
|
+
const TEXT_ANNOTATION_TEXT_STYLING_UUID = "3e6f86ab-df81-45ae-8257-e2867357df56";
|
|
409
|
+
const TEXT_ANNOTATION_TEXT_STYLING_VARIANT_UUID = "e1647bef-d801-4100-bdde-d081c422f763";
|
|
410
|
+
/**
|
|
411
|
+
* Finds a string item in an array by language code
|
|
412
|
+
*
|
|
413
|
+
* @param content - Array of string items to search
|
|
414
|
+
* @param language - Language code to search for
|
|
415
|
+
* @returns Matching string item or null if not found
|
|
416
|
+
* @internal
|
|
417
|
+
*/
|
|
418
|
+
function getStringItemByLanguage(content, language) {
|
|
419
|
+
return content.find((item) => item.lang === language) ?? null;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Parses email addresses in a string into HTML links
|
|
423
|
+
*
|
|
424
|
+
* @param string - Input string to parse
|
|
425
|
+
* @returns String with emails converted to HTML links
|
|
426
|
+
*
|
|
427
|
+
* @example
|
|
428
|
+
* ```ts
|
|
429
|
+
* const parsed = parseEmail("Contact us at info@example.com");
|
|
430
|
+
* // Returns: "Contact us at <ExternalLink href="mailto:info@example.com">info@example.com</ExternalLink>"
|
|
431
|
+
* ```
|
|
432
|
+
*/
|
|
433
|
+
function parseEmail(string) {
|
|
434
|
+
const splitString = string.split(" ");
|
|
435
|
+
const returnSplitString = [];
|
|
436
|
+
for (const string$1 of splitString) {
|
|
437
|
+
const cleanString = string$1.replaceAll(/(?<=\s|^)[([{]+|[)\]}]+(?=\s|$)/g, "").replaceAll(/[!),:;?\]]/g, "").replace(/\.$/, "");
|
|
438
|
+
const index = string$1.indexOf(cleanString);
|
|
439
|
+
const before = string$1.slice(0, index);
|
|
440
|
+
const after = string$1.slice(index + cleanString.length);
|
|
441
|
+
if (emailSchema.safeParse(cleanString).success) {
|
|
442
|
+
returnSplitString.push(`${before}<ExternalLink href="mailto:${cleanString}">${cleanString}</ExternalLink>${after}`);
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
returnSplitString.push(string$1);
|
|
446
|
+
}
|
|
447
|
+
return returnSplitString.join(" ");
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Applies text rendering options (bold, italic, underline) to a string
|
|
451
|
+
*
|
|
452
|
+
* @param contentString - The string content to render
|
|
453
|
+
* @param renderString - Space-separated string of render options
|
|
454
|
+
* @returns String with markdown formatting applied
|
|
455
|
+
* @internal
|
|
456
|
+
*/
|
|
457
|
+
function parseRenderOptions(contentString, renderString) {
|
|
458
|
+
let returnString = contentString;
|
|
459
|
+
const result = renderOptionsSchema.safeParse(renderString);
|
|
460
|
+
if (!result.success) {
|
|
461
|
+
console.warn(`Invalid render options string provided: “${renderString}”`);
|
|
462
|
+
return contentString;
|
|
463
|
+
}
|
|
464
|
+
for (const option of result.data) switch (option) {
|
|
465
|
+
case "bold":
|
|
466
|
+
returnString = `**${returnString}**`;
|
|
467
|
+
break;
|
|
468
|
+
case "italic":
|
|
469
|
+
returnString = `*${returnString}*`;
|
|
470
|
+
break;
|
|
471
|
+
case "underline":
|
|
472
|
+
returnString = `_${returnString}_`;
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
return returnString.replaceAll("'", "'");
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Applies whitespace options to a string (newline, trailing, leading)
|
|
479
|
+
*
|
|
480
|
+
* @param contentString - The string content to modify
|
|
481
|
+
* @param whitespace - Space-separated string of whitespace options
|
|
482
|
+
* @returns String with whitespace modifications applied
|
|
483
|
+
* @internal
|
|
484
|
+
*/
|
|
485
|
+
function parseWhitespace(contentString, whitespace) {
|
|
486
|
+
let returnString = contentString;
|
|
487
|
+
const result = whitespaceSchema.safeParse(whitespace);
|
|
488
|
+
if (!result.success) {
|
|
489
|
+
console.warn(`Invalid whitespace string provided: “${whitespace}”`);
|
|
490
|
+
return contentString;
|
|
491
|
+
}
|
|
492
|
+
for (const option of result.data) switch (option) {
|
|
493
|
+
case "newline":
|
|
494
|
+
returnString = `<br />\n${returnString}`;
|
|
495
|
+
break;
|
|
496
|
+
case "trailing":
|
|
497
|
+
returnString = `${returnString} `;
|
|
498
|
+
break;
|
|
499
|
+
case "leading":
|
|
500
|
+
returnString = ` ${returnString}`;
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
return returnString.replaceAll("'", "'");
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Converts a FakeString (string|number|boolean) to a proper string
|
|
507
|
+
*
|
|
508
|
+
* @param string - FakeString value to convert
|
|
509
|
+
* @returns Converted string value
|
|
510
|
+
*
|
|
511
|
+
* @example
|
|
512
|
+
* ```ts
|
|
513
|
+
* parseFakeString(true); // Returns "Yes"
|
|
514
|
+
* parseFakeString(123); // Returns "123"
|
|
515
|
+
* parseFakeString("test"); // Returns "test"
|
|
516
|
+
* ```
|
|
517
|
+
*/
|
|
518
|
+
function parseFakeString(string) {
|
|
519
|
+
return String(string).replaceAll("'", "'").replaceAll("{", String.raw`\{`).replaceAll("}", String.raw`\}`);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Parses an OchreStringItem into a formatted string
|
|
523
|
+
*
|
|
524
|
+
* @param item - OchreStringItem to parse
|
|
525
|
+
* @returns Formatted string with applied rendering and whitespace
|
|
526
|
+
*/
|
|
527
|
+
function parseStringItem(item) {
|
|
528
|
+
let returnString = "";
|
|
529
|
+
switch (typeof item.string) {
|
|
530
|
+
case "string":
|
|
531
|
+
case "number":
|
|
532
|
+
case "boolean":
|
|
533
|
+
returnString = parseFakeString(item.string);
|
|
534
|
+
break;
|
|
535
|
+
case "object": {
|
|
536
|
+
const stringItems = Array.isArray(item.string) ? item.string : [item.string];
|
|
537
|
+
for (const stringItem of stringItems) if (typeof stringItem === "string" || typeof stringItem === "number" || typeof stringItem === "boolean") returnString += parseFakeString(stringItem);
|
|
538
|
+
else if ("string" in stringItem) returnString += parseStringDocumentItem(stringItem);
|
|
539
|
+
else {
|
|
540
|
+
const renderedText = stringItem.content == null ? "" : stringItem.rend != null ? parseRenderOptions(parseFakeString(stringItem.content), stringItem.rend) : parseFakeString(stringItem.content);
|
|
541
|
+
const whitespacedText = stringItem.whitespace != null ? parseWhitespace(renderedText, stringItem.whitespace) : renderedText;
|
|
542
|
+
returnString += whitespacedText;
|
|
543
|
+
}
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
default:
|
|
547
|
+
returnString = "";
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
return returnString;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Parses rich text content into a formatted string with links and annotations
|
|
554
|
+
*
|
|
555
|
+
* @param item - Rich text item to parse
|
|
556
|
+
* @returns Formatted string with HTML/markdown elements
|
|
557
|
+
*/
|
|
558
|
+
function parseStringDocumentItem(item) {
|
|
559
|
+
if (typeof item === "string" || typeof item === "number" || typeof item === "boolean") return parseEmail(parseFakeString(item));
|
|
560
|
+
if ("whitespace" in item && !("content" in item) && !("string" in item)) if (item.whitespace === "newline") return " \n";
|
|
561
|
+
else return parseWhitespace("", item.whitespace);
|
|
562
|
+
if ("links" in item) {
|
|
563
|
+
let itemString = "";
|
|
564
|
+
if (typeof item.string === "object") itemString = parseStringContent(item.string);
|
|
565
|
+
else itemString = parseFakeString(item.string).replaceAll("<", String.raw`\<`).replaceAll("{", String.raw`\{`);
|
|
566
|
+
const itemLinks = Array.isArray(item.links) ? item.links : [item.links];
|
|
567
|
+
for (const link of itemLinks) if ("resource" in link) {
|
|
568
|
+
const linkResource = Array.isArray(link.resource) ? link.resource[0] : link.resource;
|
|
569
|
+
let linkContent = null;
|
|
570
|
+
if (linkResource.content != null) linkContent = parseFakeString(linkResource.content).replaceAll("<", String.raw`\<`).replaceAll("{", String.raw`\{`);
|
|
571
|
+
switch (linkResource.type) {
|
|
572
|
+
case "image": if (linkResource.rend === "inline") return `<InlineImage uuid="${linkResource.uuid}" ${linkContent !== null ? `content="${linkContent}"` : ""} height={${linkResource.height?.toString() ?? "null"}} width={${linkResource.width?.toString() ?? "null"}} />`;
|
|
573
|
+
else if (linkResource.publicationDateTime != null) return `<InternalLink uuid="${linkResource.uuid}">${itemString}</InternalLink>`;
|
|
574
|
+
else return `<TooltipSpan${linkContent !== null ? ` content="${linkContent}"` : ""}>${itemString}</TooltipSpan>`;
|
|
575
|
+
case "internalDocument": if ("properties" in item && item.properties != null) {
|
|
576
|
+
const itemProperty = Array.isArray(item.properties.property) ? item.properties.property[0] : item.properties.property;
|
|
577
|
+
if (itemProperty != null) {
|
|
578
|
+
const itemPropertyLabelUuid = itemProperty.label.uuid;
|
|
579
|
+
const itemPropertyValueUuid = typeof itemProperty.value === "object" && "uuid" in itemProperty.value && itemProperty.value.uuid != null ? itemProperty.value.uuid : null;
|
|
580
|
+
if (itemPropertyLabelUuid === PRESENTATION_ITEM_UUID && itemPropertyValueUuid === TEXT_ANNOTATION_UUID) {
|
|
581
|
+
if ((itemProperty.property != null ? Array.isArray(itemProperty.property) ? itemProperty.property[0] : itemProperty.property : null) != null) return `<Annotation type="hover-card" uuid="${linkResource.uuid}">${itemString}</Annotation>`;
|
|
582
|
+
}
|
|
583
|
+
return `<InternalLink uuid="${linkResource.uuid}" properties="${itemPropertyLabelUuid}"${itemPropertyValueUuid !== null ? ` value="${itemPropertyValueUuid}"` : ""}>${itemString}</InternalLink>`;
|
|
584
|
+
} else return `<InternalLink uuid="${linkResource.uuid}">${itemString}</InternalLink>`;
|
|
585
|
+
} else return `<InternalLink uuid="${linkResource.uuid}">${itemString}</InternalLink>`;
|
|
586
|
+
case "externalDocument": if (linkResource.publicationDateTime != null) return `<ExternalLink href="https:\\/\\/ochre.lib.uchicago.edu/ochre?uuid=${linkResource.uuid}&load" ${linkContent !== null ? `content="${linkContent}"` : ""}>${itemString}</ExternalLink>`;
|
|
587
|
+
else return `<TooltipSpan${linkContent !== null ? ` content="${linkContent}"` : ""}>${itemString}</TooltipSpan>`;
|
|
588
|
+
case "webpage": return `<ExternalLink href="${linkResource.href}" ${linkContent !== null ? `content="${linkContent}"` : ""}>${itemString}</ExternalLink>`;
|
|
589
|
+
default: return "";
|
|
590
|
+
}
|
|
591
|
+
} else if ("concept" in link) {
|
|
592
|
+
const linkConcept = Array.isArray(link.concept) ? link.concept[0] : link.concept;
|
|
593
|
+
if (linkConcept.publicationDateTime != null) return `<InternalLink uuid="${linkConcept.uuid}">${itemString}</InternalLink>`;
|
|
594
|
+
else return `<TooltipSpan>${itemString}</TooltipSpan>`;
|
|
595
|
+
} else if ("set" in link) {
|
|
596
|
+
const linkSet = Array.isArray(link.set) ? link.set[0] : link.set;
|
|
597
|
+
if (linkSet.publicationDateTime != null) return `<InternalLink uuid="${linkSet.uuid}">${itemString}</InternalLink>`;
|
|
598
|
+
else return `<TooltipSpan>${itemString}</TooltipSpan>`;
|
|
599
|
+
} else if ("person" in link) {
|
|
600
|
+
const linkPerson = Array.isArray(link.person) ? link.person[0] : link.person;
|
|
601
|
+
const linkContent = linkPerson.identification ? [
|
|
602
|
+
"string",
|
|
603
|
+
"number",
|
|
604
|
+
"boolean"
|
|
605
|
+
].includes(typeof linkPerson.identification.label) ? parseFakeString(linkPerson.identification.label) : parseStringContent(linkPerson.identification.label) : null;
|
|
606
|
+
if (linkPerson.publicationDateTime != null) return `<InternalLink uuid="${linkPerson.uuid}">${itemString}</InternalLink>`;
|
|
607
|
+
else return `<TooltipSpan${linkContent !== null ? ` content="${linkContent}"` : ""}>${itemString}</TooltipSpan>`;
|
|
608
|
+
} else if ("bibliography" in link) {
|
|
609
|
+
const linkBibliography = Array.isArray(link.bibliography) ? link.bibliography[0] : link.bibliography;
|
|
610
|
+
if (linkBibliography.publicationDateTime != null) return `<InternalLink uuid="${linkBibliography.uuid}">${itemString}</InternalLink>`;
|
|
611
|
+
else return `<TooltipSpan>${itemString}</TooltipSpan>`;
|
|
612
|
+
} else if ("properties" in item && item.properties != null) {
|
|
613
|
+
const itemProperty = Array.isArray(item.properties.property) ? item.properties.property[0] : item.properties.property;
|
|
614
|
+
if (itemProperty != null) {
|
|
615
|
+
const itemPropertyLabelUuid = itemProperty.label.uuid;
|
|
616
|
+
const itemPropertyValueUuid = typeof itemProperty.value === "object" && "uuid" in itemProperty.value && itemProperty.value.uuid != null ? itemProperty.value.uuid : null;
|
|
617
|
+
if (itemPropertyLabelUuid === PRESENTATION_ITEM_UUID && itemPropertyValueUuid === TEXT_ANNOTATION_UUID) {
|
|
618
|
+
const textAnnotationProperty = itemProperty.property != null ? Array.isArray(itemProperty.property) ? itemProperty.property[0] : itemProperty.property : null;
|
|
619
|
+
if (textAnnotationProperty != null) {
|
|
620
|
+
if ((typeof textAnnotationProperty.value === "object" && "uuid" in textAnnotationProperty.value && textAnnotationProperty.value.uuid != null ? textAnnotationProperty.value.uuid : null) === TEXT_ANNOTATION_TEXT_STYLING_UUID && textAnnotationProperty.property != null) {
|
|
621
|
+
const textStylingType = "text-styling";
|
|
622
|
+
let textStylingVariant = "default";
|
|
623
|
+
let textStylingSize = "md";
|
|
624
|
+
let textStylingCss = [];
|
|
625
|
+
const textStylingProperties = Array.isArray(textAnnotationProperty.property) ? textAnnotationProperty.property : [textAnnotationProperty.property];
|
|
626
|
+
if (textStylingProperties.length > 0) {
|
|
627
|
+
const textStylingVariantProperty = textStylingProperties.find((property) => property.label.uuid === TEXT_ANNOTATION_TEXT_STYLING_VARIANT_UUID);
|
|
628
|
+
if (textStylingVariantProperty != null) {
|
|
629
|
+
const textStylingPropertyVariant = parseFakeString(textStylingVariantProperty.value.content);
|
|
630
|
+
const textStylingSizeProperty = textStylingVariantProperty.property != null ? Array.isArray(textStylingVariantProperty.property) ? textStylingVariantProperty.property[0] : textStylingVariantProperty.property : null;
|
|
631
|
+
if (textStylingSizeProperty != null) textStylingSize = parseFakeString(textStylingSizeProperty.value.content);
|
|
632
|
+
textStylingVariant = textStylingPropertyVariant;
|
|
633
|
+
}
|
|
634
|
+
const textStylingCssProperties = textStylingProperties.filter((property) => property.label.uuid !== TEXT_ANNOTATION_TEXT_STYLING_VARIANT_UUID);
|
|
635
|
+
if (textStylingCssProperties.length > 0) textStylingCss = textStylingCssProperties.map((property) => ({
|
|
636
|
+
label: parseFakeString(property.label.content),
|
|
637
|
+
value: parseFakeString(property.value.content)
|
|
638
|
+
}));
|
|
639
|
+
}
|
|
640
|
+
return `<Annotation type="${textStylingType}" variant="${textStylingVariant}" size="${textStylingSize}"${textStylingCss.length > 0 ? ` cssStyles={{default: ${JSON.stringify(textStylingCss)}, tablet: [], mobile: []}}` : ""}>${itemString}</Annotation>`;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
let returnString = "";
|
|
648
|
+
if ("string" in item) {
|
|
649
|
+
const stringItems = Array.isArray(item.string) ? item.string : [item.string];
|
|
650
|
+
for (const stringItem of stringItems) returnString += parseStringDocumentItem(stringItem);
|
|
651
|
+
if ("whitespace" in item && item.whitespace != null) returnString = parseWhitespace(parseEmail(returnString), item.whitespace);
|
|
652
|
+
return returnString.replaceAll("'", "'");
|
|
653
|
+
} else {
|
|
654
|
+
returnString = parseFakeString(item.content);
|
|
655
|
+
if (item.rend != null) returnString = parseRenderOptions(parseEmail(returnString), item.rend);
|
|
656
|
+
if (item.whitespace != null) returnString = parseWhitespace(parseEmail(returnString), item.whitespace);
|
|
657
|
+
}
|
|
658
|
+
return returnString;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Parses raw string content into a formatted string
|
|
662
|
+
*
|
|
663
|
+
* @param content - Raw string content to parse
|
|
664
|
+
* @param language - Optional language code for content selection (defaults to "eng")
|
|
665
|
+
* @returns Parsed and formatted string
|
|
666
|
+
*/
|
|
667
|
+
function parseStringContent(content, language = "eng") {
|
|
668
|
+
switch (typeof content.content) {
|
|
669
|
+
case "string":
|
|
670
|
+
case "number":
|
|
671
|
+
case "boolean":
|
|
672
|
+
if (content.rend != null) return parseRenderOptions(parseFakeString(content.content), content.rend);
|
|
673
|
+
return parseFakeString(content.content);
|
|
674
|
+
case "object": if (Array.isArray(content.content)) {
|
|
675
|
+
const stringItem = getStringItemByLanguage(content.content, language);
|
|
676
|
+
if (stringItem) return parseStringItem(stringItem);
|
|
677
|
+
else {
|
|
678
|
+
const returnStringItem = content.content[0];
|
|
679
|
+
if (!returnStringItem) throw new Error(`No string item found for language “${language}” in the following content:\n${JSON.stringify(content.content)}.`);
|
|
680
|
+
return parseStringItem(returnStringItem);
|
|
681
|
+
}
|
|
682
|
+
} else return parseStringItem(content.content);
|
|
683
|
+
default: return String(content.content);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
//#endregion
|
|
688
|
+
//#region src/utils/helpers.ts
|
|
689
|
+
/**
|
|
690
|
+
* Get the category of an item from the OCHRE API response
|
|
691
|
+
* @param keys - The keys of the OCHRE API response
|
|
692
|
+
* @returns The category of the item
|
|
693
|
+
* @internal
|
|
694
|
+
*/
|
|
695
|
+
function getItemCategory(keys) {
|
|
696
|
+
const categoryFound = keys.find((key) => categorySchema.safeParse(key).success);
|
|
697
|
+
if (!categoryFound) {
|
|
698
|
+
const unknownKey = keys.find((key) => ![
|
|
699
|
+
"uuid",
|
|
700
|
+
"uuidBelongsTo",
|
|
701
|
+
"belongsTo",
|
|
702
|
+
"publicationDateTime",
|
|
703
|
+
"metadata",
|
|
704
|
+
"languages"
|
|
705
|
+
].includes(key));
|
|
706
|
+
throw new Error(`Invalid OCHRE data; found unexpected "${unknownKey}" key`);
|
|
707
|
+
}
|
|
708
|
+
return categorySchema.parse(categoryFound);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
//#endregion
|
|
712
|
+
//#region src/utils/parse.ts
|
|
713
|
+
/**
|
|
714
|
+
* Parses raw identification data into the standardized Identification type
|
|
715
|
+
*
|
|
716
|
+
* @param identification - Raw identification data from OCHRE format
|
|
717
|
+
* @returns Parsed Identification object with label and abbreviation
|
|
718
|
+
*/
|
|
719
|
+
function parseIdentification(identification) {
|
|
720
|
+
try {
|
|
721
|
+
const returnIdentification = {
|
|
722
|
+
label: [
|
|
723
|
+
"string",
|
|
724
|
+
"number",
|
|
725
|
+
"boolean"
|
|
726
|
+
].includes(typeof identification.label) ? parseFakeString(identification.label) : parseStringContent(identification.label),
|
|
727
|
+
abbreviation: "",
|
|
728
|
+
code: identification.code ?? null
|
|
729
|
+
};
|
|
730
|
+
for (const key of Object.keys(identification).filter((key$1) => key$1 !== "label" && key$1 !== "code")) returnIdentification[key] = typeof identification[key] === "string" ? parseFakeString(identification[key]) : parseStringContent(identification[key]);
|
|
731
|
+
return returnIdentification;
|
|
732
|
+
} catch (error) {
|
|
733
|
+
console.error(error);
|
|
734
|
+
return {
|
|
735
|
+
label: "",
|
|
736
|
+
abbreviation: "",
|
|
737
|
+
code: null
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Parses raw language data into an array of language codes
|
|
743
|
+
*
|
|
744
|
+
* @param language - Raw language data, either single or array
|
|
745
|
+
* @returns Array of language codes as strings
|
|
746
|
+
*/
|
|
747
|
+
function parseLanguages(language) {
|
|
748
|
+
if (language == null) return ["eng"];
|
|
749
|
+
if (Array.isArray(language)) return language.map((lang) => parseStringContent(lang));
|
|
750
|
+
else return [parseStringContent(language)];
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Parses raw metadata into the standardized Metadata type
|
|
754
|
+
*
|
|
755
|
+
* @param metadata - Raw metadata from OCHRE format
|
|
756
|
+
* @returns Parsed Metadata object
|
|
757
|
+
*/
|
|
758
|
+
function parseMetadata(metadata) {
|
|
759
|
+
let identification = {
|
|
760
|
+
label: "",
|
|
761
|
+
abbreviation: "",
|
|
762
|
+
code: null
|
|
763
|
+
};
|
|
764
|
+
if (metadata.item) if (metadata.item.label || metadata.item.abbreviation) {
|
|
765
|
+
let label = "";
|
|
766
|
+
let abbreviation = "";
|
|
767
|
+
let code = null;
|
|
768
|
+
if (metadata.item.label) label = parseStringContent(metadata.item.label);
|
|
769
|
+
if (metadata.item.abbreviation) abbreviation = parseStringContent(metadata.item.abbreviation);
|
|
770
|
+
if (metadata.item.identification.code) code = metadata.item.identification.code;
|
|
771
|
+
identification = {
|
|
772
|
+
label,
|
|
773
|
+
abbreviation,
|
|
774
|
+
code
|
|
775
|
+
};
|
|
776
|
+
} else identification = parseIdentification(metadata.item.identification);
|
|
777
|
+
let projectIdentification = null;
|
|
778
|
+
const baseProjectIdentification = metadata.project?.identification ? parseIdentification(metadata.project.identification) : null;
|
|
779
|
+
if (baseProjectIdentification) projectIdentification = {
|
|
780
|
+
...baseProjectIdentification,
|
|
781
|
+
website: metadata.project?.identification.website ?? null
|
|
782
|
+
};
|
|
783
|
+
return {
|
|
784
|
+
project: projectIdentification ? { identification: projectIdentification } : null,
|
|
785
|
+
item: metadata.item ? {
|
|
786
|
+
identification,
|
|
787
|
+
category: metadata.item.category,
|
|
788
|
+
type: metadata.item.type,
|
|
789
|
+
maxLength: metadata.item.maxLength ?? null
|
|
790
|
+
} : null,
|
|
791
|
+
dataset: parseStringContent(metadata.dataset),
|
|
792
|
+
publisher: parseStringContent(metadata.publisher),
|
|
793
|
+
languages: parseLanguages(metadata.language),
|
|
794
|
+
identifier: parseStringContent(metadata.identifier),
|
|
795
|
+
description: parseStringContent(metadata.description)
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Parses raw context item data into the standardized ContextItem type
|
|
800
|
+
*
|
|
801
|
+
* @param contextItem - Raw context item data from OCHRE format
|
|
802
|
+
* @returns Parsed ContextItem object
|
|
803
|
+
*/
|
|
804
|
+
function parseContextItem(contextItem) {
|
|
805
|
+
return {
|
|
806
|
+
uuid: contextItem.uuid,
|
|
807
|
+
publicationDateTime: contextItem.publicationDateTime != null ? new Date(contextItem.publicationDateTime) : null,
|
|
808
|
+
number: contextItem.n,
|
|
809
|
+
content: parseFakeString(contextItem.content)
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Parses raw context data into the standardized Context type
|
|
814
|
+
*
|
|
815
|
+
* @param context - Raw context data from OCHRE format
|
|
816
|
+
* @returns Parsed Context object
|
|
817
|
+
*/
|
|
818
|
+
function parseContext(context) {
|
|
819
|
+
return {
|
|
820
|
+
nodes: (Array.isArray(context.context) ? context.context : [context.context]).map((context$1) => {
|
|
821
|
+
const spatialUnit = [];
|
|
822
|
+
if ("spatialUnit" in context$1 && context$1.spatialUnit) {
|
|
823
|
+
const contextsToParse = Array.isArray(context$1.spatialUnit) ? context$1.spatialUnit : [context$1.spatialUnit];
|
|
824
|
+
for (const contextItem of contextsToParse) spatialUnit.push(parseContextItem(contextItem));
|
|
825
|
+
}
|
|
826
|
+
return {
|
|
827
|
+
tree: parseContextItem(context$1.tree),
|
|
828
|
+
project: parseContextItem(context$1.project),
|
|
829
|
+
spatialUnit
|
|
830
|
+
};
|
|
831
|
+
}),
|
|
832
|
+
displayPath: context.displayPath
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Parses raw license data into the standardized License type
|
|
837
|
+
*
|
|
838
|
+
* @param license - Raw license data from OCHRE format
|
|
839
|
+
* @returns Parsed License object or null if invalid
|
|
840
|
+
*/
|
|
841
|
+
function parseLicense(license) {
|
|
842
|
+
if (typeof license.license === "string") return null;
|
|
843
|
+
return {
|
|
844
|
+
content: license.license.content,
|
|
845
|
+
url: license.license.target
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Parses raw person data into the standardized Person type
|
|
850
|
+
*
|
|
851
|
+
* @param person - Raw person data from OCHRE format
|
|
852
|
+
* @returns Parsed Person object
|
|
853
|
+
*/
|
|
854
|
+
function parsePerson(person) {
|
|
855
|
+
return {
|
|
856
|
+
uuid: person.uuid,
|
|
857
|
+
category: "person",
|
|
858
|
+
publicationDateTime: person.publicationDateTime != null ? new Date(person.publicationDateTime) : null,
|
|
859
|
+
type: person.type ?? null,
|
|
860
|
+
number: person.n ?? null,
|
|
861
|
+
context: person.context ? parseContext(person.context) : null,
|
|
862
|
+
date: person.date ?? null,
|
|
863
|
+
identification: person.identification ? parseIdentification(person.identification) : null,
|
|
864
|
+
availability: person.availability ? parseLicense(person.availability) : null,
|
|
865
|
+
image: person.image ? parseImage(person.image) : null,
|
|
866
|
+
address: person.address ? {
|
|
867
|
+
country: person.address.country ?? null,
|
|
868
|
+
city: person.address.city ?? null,
|
|
869
|
+
state: person.address.state ?? null
|
|
870
|
+
} : null,
|
|
871
|
+
description: person.description ? typeof person.description === "string" || typeof person.description === "number" || typeof person.description === "boolean" ? parseFakeString(person.description) : parseStringContent(person.description) : null,
|
|
872
|
+
coordinates: parseCoordinates(person.coordinates),
|
|
873
|
+
content: person.content != null ? parseFakeString(person.content) : null,
|
|
874
|
+
notes: person.notes ? parseNotes(Array.isArray(person.notes.note) ? person.notes.note : [person.notes.note]) : [],
|
|
875
|
+
events: person.events ? parseEvents(Array.isArray(person.events.event) ? person.events.event : [person.events.event]) : [],
|
|
876
|
+
properties: person.properties ? parseProperties(Array.isArray(person.properties.property) ? person.properties.property : [person.properties.property]) : [],
|
|
877
|
+
bibliographies: person.bibliographies ? parseBibliographies(Array.isArray(person.bibliographies.bibliography) ? person.bibliographies.bibliography : [person.bibliographies.bibliography]) : []
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Parses raw person data into the standardized Person type
|
|
882
|
+
*
|
|
883
|
+
* @param persons - Array of raw person data from OCHRE format
|
|
884
|
+
* @returns Array of parsed Person objects
|
|
885
|
+
*/
|
|
886
|
+
function parsePersons(persons) {
|
|
887
|
+
const returnPersons = [];
|
|
888
|
+
for (const person of persons) returnPersons.push(parsePerson(person));
|
|
889
|
+
return returnPersons;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Parses an array of raw links into standardized Link objects
|
|
893
|
+
*
|
|
894
|
+
* @param linkRaw - Raw OCHRE link
|
|
895
|
+
* @returns Parsed Link object
|
|
896
|
+
*/
|
|
897
|
+
function parseLink(linkRaw) {
|
|
898
|
+
const links = "resource" in linkRaw ? linkRaw.resource : "spatialUnit" in linkRaw ? linkRaw.spatialUnit : "concept" in linkRaw ? linkRaw.concept : "set" in linkRaw ? linkRaw.set : "tree" in linkRaw ? linkRaw.tree : "person" in linkRaw ? linkRaw.person : "bibliography" in linkRaw ? linkRaw.bibliography : "propertyValue" in linkRaw ? linkRaw.propertyValue : null;
|
|
899
|
+
if (!links) throw new Error(`Invalid link provided: ${JSON.stringify(linkRaw, null, 2)}`);
|
|
900
|
+
const linksToParse = Array.isArray(links) ? links : [links];
|
|
901
|
+
const returnLinks = [];
|
|
902
|
+
for (const link of linksToParse) {
|
|
903
|
+
const returnLink = {
|
|
904
|
+
category: "resource" in linkRaw ? "resource" : "spatialUnit" in linkRaw ? "spatialUnit" : "concept" in linkRaw ? "concept" : "set" in linkRaw ? "set" : "person" in linkRaw ? "person" : "tree" in linkRaw ? "tree" : "bibliography" in linkRaw ? "bibliography" : "propertyValue" in linkRaw ? "propertyValue" : null,
|
|
905
|
+
content: "content" in link ? link.content != null ? parseFakeString(link.content) : null : null,
|
|
906
|
+
href: "href" in link && link.href != null ? link.href : null,
|
|
907
|
+
fileFormat: "fileFormat" in link && link.fileFormat != null ? link.fileFormat : null,
|
|
908
|
+
fileSize: "fileSize" in link && link.fileSize != null ? link.fileSize : null,
|
|
909
|
+
uuid: link.uuid ?? null,
|
|
910
|
+
type: link.type ?? null,
|
|
911
|
+
identification: link.identification ? parseIdentification(link.identification) : null,
|
|
912
|
+
description: "description" in link && link.description != null ? parseStringContent(link.description) : null,
|
|
913
|
+
image: null,
|
|
914
|
+
bibliographies: "bibliography" in linkRaw ? parseBibliographies(Array.isArray(linkRaw.bibliography) ? linkRaw.bibliography : [linkRaw.bibliography]) : null,
|
|
915
|
+
publicationDateTime: link.publicationDateTime != null ? new Date(link.publicationDateTime) : null
|
|
916
|
+
};
|
|
917
|
+
if ("height" in link && link.height != null && link.width != null && link.heightPreview != null && link.widthPreview != null) returnLink.image = {
|
|
918
|
+
isInline: link.rend === "inline",
|
|
919
|
+
isPrimary: link.isPrimary ?? false,
|
|
920
|
+
heightPreview: link.heightPreview,
|
|
921
|
+
widthPreview: link.widthPreview,
|
|
922
|
+
height: link.height,
|
|
923
|
+
width: link.width
|
|
924
|
+
};
|
|
925
|
+
returnLinks.push(returnLink);
|
|
926
|
+
}
|
|
927
|
+
return returnLinks;
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Parses an array of raw links into standardized Link objects
|
|
931
|
+
*
|
|
932
|
+
* @param links - Array of raw OCHRE links
|
|
933
|
+
* @returns Array of parsed Link objects
|
|
934
|
+
*/
|
|
935
|
+
function parseLinks(links) {
|
|
936
|
+
const returnLinks = [];
|
|
937
|
+
for (const link of links) returnLinks.push(...parseLink(link));
|
|
938
|
+
return returnLinks;
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Parses raw document content into a standardized Document structure
|
|
942
|
+
*
|
|
943
|
+
* @param document - Raw document content in OCHRE format
|
|
944
|
+
* @param language - Language code to use for content selection (defaults to "eng")
|
|
945
|
+
* @returns Parsed Document object with content and footnotes
|
|
946
|
+
*/
|
|
947
|
+
function parseDocument(document, language = "eng") {
|
|
948
|
+
let returnString = "";
|
|
949
|
+
const documentWithLanguage = Array.isArray(document) ? document.find((doc) => doc.lang === language) : document;
|
|
950
|
+
if (typeof documentWithLanguage.string === "string" || typeof documentWithLanguage.string === "number" || typeof documentWithLanguage.string === "boolean") returnString += parseEmail(parseFakeString(documentWithLanguage.string));
|
|
951
|
+
else {
|
|
952
|
+
const documentItems = Array.isArray(documentWithLanguage.string) ? documentWithLanguage.string : [documentWithLanguage.string];
|
|
953
|
+
for (const item of documentItems) returnString += parseStringDocumentItem(item);
|
|
954
|
+
}
|
|
955
|
+
return returnString;
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Parses raw image data into a standardized Image structure
|
|
959
|
+
*
|
|
960
|
+
* @param image - Raw image data in OCHRE format
|
|
961
|
+
* @returns Parsed Image object or null if invalid
|
|
962
|
+
*/
|
|
963
|
+
function parseImage(image) {
|
|
964
|
+
return {
|
|
965
|
+
publicationDateTime: image.publicationDateTime != null ? new Date(image.publicationDateTime) : null,
|
|
966
|
+
identification: image.identification ? parseIdentification(image.identification) : null,
|
|
967
|
+
url: image.href ?? (image.htmlImgSrcPrefix == null && image.content != null ? parseFakeString(image.content) : null),
|
|
968
|
+
htmlPrefix: image.htmlImgSrcPrefix ?? null,
|
|
969
|
+
content: image.htmlImgSrcPrefix != null && image.content != null ? parseFakeString(image.content) : null,
|
|
970
|
+
widthPreview: image.widthPreview ?? null,
|
|
971
|
+
heightPreview: image.heightPreview ?? null,
|
|
972
|
+
width: image.width ?? null,
|
|
973
|
+
height: image.height ?? null
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Parses raw notes into standardized Note objects
|
|
978
|
+
*
|
|
979
|
+
* @param notes - Array of raw notes in OCHRE format
|
|
980
|
+
* @param language - Language code for content selection (defaults to "eng")
|
|
981
|
+
* @returns Array of parsed Note objects
|
|
982
|
+
*/
|
|
983
|
+
function parseNotes(notes, language = "eng") {
|
|
984
|
+
const returnNotes = [];
|
|
985
|
+
for (const note of notes) {
|
|
986
|
+
if (typeof note === "string") {
|
|
987
|
+
if (note === "") continue;
|
|
988
|
+
returnNotes.push({
|
|
989
|
+
number: -1,
|
|
990
|
+
title: null,
|
|
991
|
+
date: null,
|
|
992
|
+
authors: [],
|
|
993
|
+
content: note
|
|
994
|
+
});
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
let content = "";
|
|
998
|
+
const notesToParse = note.content != null ? Array.isArray(note.content) ? note.content : [note.content] : [];
|
|
999
|
+
if (notesToParse.length === 0) continue;
|
|
1000
|
+
let noteWithLanguage = notesToParse.find((item) => item.lang === language);
|
|
1001
|
+
if (!noteWithLanguage) {
|
|
1002
|
+
noteWithLanguage = notesToParse[0];
|
|
1003
|
+
if (!noteWithLanguage) throw new Error(`Note does not have a valid content item: ${JSON.stringify(note, null, 2)}`);
|
|
1004
|
+
}
|
|
1005
|
+
if (typeof noteWithLanguage.string === "string" || typeof noteWithLanguage.string === "number" || typeof noteWithLanguage.string === "boolean") content = parseEmail(parseFakeString(noteWithLanguage.string));
|
|
1006
|
+
else content = parseEmail(parseDocument(noteWithLanguage));
|
|
1007
|
+
returnNotes.push({
|
|
1008
|
+
number: note.noteNo,
|
|
1009
|
+
title: noteWithLanguage.title != null ? parseFakeString(noteWithLanguage.title) : null,
|
|
1010
|
+
date: note.date ?? null,
|
|
1011
|
+
authors: note.authors ? parsePersons(Array.isArray(note.authors.author) ? note.authors.author : [note.authors.author]) : [],
|
|
1012
|
+
content
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
return returnNotes;
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Parses raw coordinates data into a standardized Coordinates structure
|
|
1019
|
+
*
|
|
1020
|
+
* @param coordinates - Raw coordinates data in OCHRE format
|
|
1021
|
+
* @returns Parsed Coordinates object
|
|
1022
|
+
*/
|
|
1023
|
+
function parseCoordinates(coordinates) {
|
|
1024
|
+
if (coordinates == null) return [];
|
|
1025
|
+
const returnCoordinates = [];
|
|
1026
|
+
const coordsToParse = Array.isArray(coordinates.coord) ? coordinates.coord : [coordinates.coord];
|
|
1027
|
+
for (const coord of coordsToParse) {
|
|
1028
|
+
const source = "source" in coord && coord.source ? coord.source.context === "self" ? {
|
|
1029
|
+
context: "self",
|
|
1030
|
+
uuid: coord.source.label.uuid,
|
|
1031
|
+
label: parseStringContent(coord.source.label)
|
|
1032
|
+
} : coord.source.context === "related" ? {
|
|
1033
|
+
context: "related",
|
|
1034
|
+
uuid: coord.source.label.uuid,
|
|
1035
|
+
label: parseStringContent(coord.source.label),
|
|
1036
|
+
value: parseStringContent(coord.source.value)
|
|
1037
|
+
} : {
|
|
1038
|
+
context: "inherited",
|
|
1039
|
+
uuid: coord.source.label.uuid,
|
|
1040
|
+
item: {
|
|
1041
|
+
uuid: coord.source.item.label.uuid,
|
|
1042
|
+
label: parseStringContent(coord.source.item.label)
|
|
1043
|
+
},
|
|
1044
|
+
label: parseStringContent(coord.source.label)
|
|
1045
|
+
} : null;
|
|
1046
|
+
switch (coord.type) {
|
|
1047
|
+
case "point":
|
|
1048
|
+
returnCoordinates.push({
|
|
1049
|
+
type: coord.type,
|
|
1050
|
+
latitude: coord.latitude,
|
|
1051
|
+
longitude: coord.longitude,
|
|
1052
|
+
altitude: coord.altitude ?? null,
|
|
1053
|
+
source
|
|
1054
|
+
});
|
|
1055
|
+
break;
|
|
1056
|
+
case "plane":
|
|
1057
|
+
returnCoordinates.push({
|
|
1058
|
+
type: coord.type,
|
|
1059
|
+
minimum: {
|
|
1060
|
+
latitude: coord.minimum.latitude,
|
|
1061
|
+
longitude: coord.minimum.longitude
|
|
1062
|
+
},
|
|
1063
|
+
maximum: {
|
|
1064
|
+
latitude: coord.maximum.latitude,
|
|
1065
|
+
longitude: coord.maximum.longitude
|
|
1066
|
+
},
|
|
1067
|
+
source
|
|
1068
|
+
});
|
|
1069
|
+
break;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
return returnCoordinates;
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Parses a raw observation into a standardized Observation structure
|
|
1076
|
+
*
|
|
1077
|
+
* @param observation - Raw observation data in OCHRE format
|
|
1078
|
+
* @returns Parsed Observation object
|
|
1079
|
+
*/
|
|
1080
|
+
function parseObservation(observation) {
|
|
1081
|
+
return {
|
|
1082
|
+
number: observation.observationNo,
|
|
1083
|
+
date: observation.date ?? null,
|
|
1084
|
+
observers: observation.observers != null ? typeof observation.observers === "string" || typeof observation.observers === "number" || typeof observation.observers === "boolean" ? parseFakeString(observation.observers).split(";").map((observer) => observer.trim()) : parsePersons(Array.isArray(observation.observers) ? observation.observers : [observation.observers]) : [],
|
|
1085
|
+
notes: observation.notes ? parseNotes(Array.isArray(observation.notes.note) ? observation.notes.note : [observation.notes.note]) : [],
|
|
1086
|
+
links: observation.links ? parseLinks(Array.isArray(observation.links) ? observation.links : [observation.links]) : [],
|
|
1087
|
+
properties: observation.properties ? parseProperties(Array.isArray(observation.properties.property) ? observation.properties.property : [observation.properties.property]) : [],
|
|
1088
|
+
bibliographies: observation.bibliographies ? parseBibliographies(Array.isArray(observation.bibliographies.bibliography) ? observation.bibliographies.bibliography : [observation.bibliographies.bibliography]) : []
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Parses an array of raw observations into standardized Observation objects
|
|
1093
|
+
*
|
|
1094
|
+
* @param observations - Array of raw observations in OCHRE format
|
|
1095
|
+
* @returns Array of parsed Observation objects
|
|
1096
|
+
*/
|
|
1097
|
+
function parseObservations(observations) {
|
|
1098
|
+
const returnObservations = [];
|
|
1099
|
+
for (const observation of observations) returnObservations.push(parseObservation(observation));
|
|
1100
|
+
return returnObservations;
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Parses an array of raw events into standardized Event objects
|
|
1104
|
+
*
|
|
1105
|
+
* @param events - Array of raw events in OCHRE format
|
|
1106
|
+
* @returns Array of parsed Event objects
|
|
1107
|
+
*/
|
|
1108
|
+
function parseEvents(events) {
|
|
1109
|
+
const returnEvents = [];
|
|
1110
|
+
for (const event of events) returnEvents.push({
|
|
1111
|
+
date: event.dateTime != null ? new Date(event.dateTime) : null,
|
|
1112
|
+
label: parseStringContent(event.label),
|
|
1113
|
+
location: event.location ? {
|
|
1114
|
+
uuid: event.location.uuid,
|
|
1115
|
+
content: parseStringContent(event.location)
|
|
1116
|
+
} : null,
|
|
1117
|
+
agent: event.agent ? {
|
|
1118
|
+
uuid: event.agent.uuid,
|
|
1119
|
+
content: parseStringContent(event.agent)
|
|
1120
|
+
} : null,
|
|
1121
|
+
comment: event.comment ? parseStringContent(event.comment) : null,
|
|
1122
|
+
value: event.value ? parseFakeString(event.value) : null
|
|
1123
|
+
});
|
|
1124
|
+
return returnEvents;
|
|
1125
|
+
}
|
|
1126
|
+
function parseProperty(property, language = "eng") {
|
|
1127
|
+
const values = ("value" in property && property.value ? Array.isArray(property.value) ? property.value : [property.value] : []).map((value) => {
|
|
1128
|
+
let content = null;
|
|
1129
|
+
let label = null;
|
|
1130
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1131
|
+
content = parseFakeString(value);
|
|
1132
|
+
return {
|
|
1133
|
+
content,
|
|
1134
|
+
label: null,
|
|
1135
|
+
dataType: "string",
|
|
1136
|
+
isUncertain: false,
|
|
1137
|
+
category: "value",
|
|
1138
|
+
type: null,
|
|
1139
|
+
uuid: null,
|
|
1140
|
+
publicationDateTime: null,
|
|
1141
|
+
unit: null,
|
|
1142
|
+
href: null,
|
|
1143
|
+
slug: null
|
|
1144
|
+
};
|
|
1145
|
+
} else {
|
|
1146
|
+
let parsedType = "string";
|
|
1147
|
+
if (value.dataType != null) {
|
|
1148
|
+
const { data, error } = propertyValueContentTypeSchema.safeParse(value.dataType);
|
|
1149
|
+
if (error) throw new Error(`Invalid property value content type: "${value.dataType}"`);
|
|
1150
|
+
parsedType = data;
|
|
1151
|
+
}
|
|
1152
|
+
switch (parsedType) {
|
|
1153
|
+
case "integer":
|
|
1154
|
+
case "decimal":
|
|
1155
|
+
case "time":
|
|
1156
|
+
if (value.rawValue != null) {
|
|
1157
|
+
content = Number(value.rawValue);
|
|
1158
|
+
label = value.content ? parseStringContent({ content: value.content }) : null;
|
|
1159
|
+
} else {
|
|
1160
|
+
content = Number(value.content);
|
|
1161
|
+
label = null;
|
|
1162
|
+
}
|
|
1163
|
+
break;
|
|
1164
|
+
case "boolean":
|
|
1165
|
+
if (value.rawValue != null) {
|
|
1166
|
+
content = value.rawValue === "true";
|
|
1167
|
+
label = value.content ? parseStringContent({ content: value.content }) : null;
|
|
1168
|
+
} else {
|
|
1169
|
+
content = value.content === true;
|
|
1170
|
+
label = null;
|
|
1171
|
+
}
|
|
1172
|
+
break;
|
|
1173
|
+
default:
|
|
1174
|
+
if ("slug" in value && value.slug != null) content = parseFakeString(value.slug);
|
|
1175
|
+
else if (value.content != null) if (value.rawValue != null) {
|
|
1176
|
+
content = parseFakeString(value.rawValue);
|
|
1177
|
+
label = value.content ? parseStringContent({ content: value.content }) : null;
|
|
1178
|
+
} else {
|
|
1179
|
+
content = parseStringContent({ content: value.content });
|
|
1180
|
+
label = null;
|
|
1181
|
+
}
|
|
1182
|
+
break;
|
|
1183
|
+
}
|
|
1184
|
+
return {
|
|
1185
|
+
content,
|
|
1186
|
+
dataType: parsedType,
|
|
1187
|
+
isUncertain: value.isUncertain ?? false,
|
|
1188
|
+
label,
|
|
1189
|
+
category: value.category ?? null,
|
|
1190
|
+
type: value.type ?? null,
|
|
1191
|
+
uuid: value.uuid ?? null,
|
|
1192
|
+
publicationDateTime: value.publicationDateTime != null ? new Date(value.publicationDateTime) : null,
|
|
1193
|
+
unit: value.unit ?? null,
|
|
1194
|
+
href: value.href ?? null,
|
|
1195
|
+
slug: value.slug ?? null
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
return {
|
|
1200
|
+
uuid: property.label.uuid,
|
|
1201
|
+
label: parseStringContent(property.label, language).replace(/\s*\.{3}$/, "").trim(),
|
|
1202
|
+
values,
|
|
1203
|
+
comment: property.comment != null ? parseStringContent(property.comment) : null,
|
|
1204
|
+
properties: property.property ? parseProperties(Array.isArray(property.property) ? property.property : [property.property]) : []
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Parses raw properties into standardized Property objects
|
|
1209
|
+
*
|
|
1210
|
+
* @param properties - Array of raw properties in OCHRE format
|
|
1211
|
+
* @param language - Language code for content selection (defaults to "eng")
|
|
1212
|
+
* @returns Array of parsed Property objects
|
|
1213
|
+
*/
|
|
1214
|
+
function parseProperties(properties, language = "eng") {
|
|
1215
|
+
const returnProperties = [];
|
|
1216
|
+
for (const property of properties) returnProperties.push(parseProperty(property, language));
|
|
1217
|
+
return returnProperties;
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Parses raw interpretations into standardized Interpretation objects
|
|
1221
|
+
*
|
|
1222
|
+
* @param interpretations - Array of raw interpretations in OCHRE format
|
|
1223
|
+
* @returns Array of parsed Interpretation objects
|
|
1224
|
+
*/
|
|
1225
|
+
function parseInterpretations(interpretations) {
|
|
1226
|
+
const returnInterpretations = [];
|
|
1227
|
+
for (const interpretation of interpretations) returnInterpretations.push({
|
|
1228
|
+
date: interpretation.date,
|
|
1229
|
+
number: interpretation.interpretationNo,
|
|
1230
|
+
properties: interpretation.properties ? parseProperties(Array.isArray(interpretation.properties.property) ? interpretation.properties.property : [interpretation.properties.property]) : [],
|
|
1231
|
+
bibliographies: interpretation.bibliographies ? parseBibliographies(Array.isArray(interpretation.bibliographies.bibliography) ? interpretation.bibliographies.bibliography : [interpretation.bibliographies.bibliography]) : []
|
|
1232
|
+
});
|
|
1233
|
+
return returnInterpretations;
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Parses raw image map data into a standardized ImageMap structure
|
|
1237
|
+
*
|
|
1238
|
+
* @param imageMap - Raw image map data in OCHRE format
|
|
1239
|
+
* @returns Parsed ImageMap object
|
|
1240
|
+
*/
|
|
1241
|
+
function parseImageMap(imageMap) {
|
|
1242
|
+
const returnImageMap = {
|
|
1243
|
+
area: [],
|
|
1244
|
+
width: imageMap.width,
|
|
1245
|
+
height: imageMap.height
|
|
1246
|
+
};
|
|
1247
|
+
const imageMapAreasToParse = Array.isArray(imageMap.area) ? imageMap.area : [imageMap.area];
|
|
1248
|
+
for (const area of imageMapAreasToParse) returnImageMap.area.push({
|
|
1249
|
+
uuid: area.uuid,
|
|
1250
|
+
publicationDateTime: area.publicationDateTime != null ? new Date(area.publicationDateTime) : null,
|
|
1251
|
+
type: area.type,
|
|
1252
|
+
title: parseFakeString(area.title),
|
|
1253
|
+
shape: area.shape === "rect" ? "rectangle" : area.shape === "circle" ? "circle" : "polygon",
|
|
1254
|
+
coords: area.coords.split(",").map((coord) => Number.parseInt(coord)),
|
|
1255
|
+
slug: area.slug ? parseFakeString(area.slug) : null
|
|
1256
|
+
});
|
|
1257
|
+
return returnImageMap;
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Parses raw period data into a standardized Period structure
|
|
1261
|
+
*
|
|
1262
|
+
* @param period - Raw period data in OCHRE format
|
|
1263
|
+
* @returns Parsed Period object
|
|
1264
|
+
*/
|
|
1265
|
+
function parsePeriod(period) {
|
|
1266
|
+
return {
|
|
1267
|
+
uuid: period.uuid,
|
|
1268
|
+
category: "period",
|
|
1269
|
+
publicationDateTime: period.publicationDateTime != null ? new Date(period.publicationDateTime) : null,
|
|
1270
|
+
type: period.type ?? null,
|
|
1271
|
+
number: period.n ?? null,
|
|
1272
|
+
identification: parseIdentification(period.identification),
|
|
1273
|
+
description: period.description ? parseStringContent(period.description) : null
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Parses an array of raw periods into standardized Period objects
|
|
1278
|
+
*
|
|
1279
|
+
* @param periods - Array of raw periods in OCHRE format
|
|
1280
|
+
* @returns Array of parsed Period objects
|
|
1281
|
+
*/
|
|
1282
|
+
function parsePeriods(periods) {
|
|
1283
|
+
const returnPeriods = [];
|
|
1284
|
+
for (const period of periods) returnPeriods.push(parsePeriod(period));
|
|
1285
|
+
return returnPeriods;
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Parses raw bibliography data into a standardized Bibliography structure
|
|
1289
|
+
*
|
|
1290
|
+
* @param bibliography - Raw bibliography data in OCHRE format
|
|
1291
|
+
* @returns Parsed Bibliography object
|
|
1292
|
+
*/
|
|
1293
|
+
function parseBibliography(bibliography) {
|
|
1294
|
+
let resource = null;
|
|
1295
|
+
if (bibliography.source?.resource) resource = {
|
|
1296
|
+
uuid: bibliography.source.resource.uuid,
|
|
1297
|
+
publicationDateTime: bibliography.source.resource.publicationDateTime ? new Date(bibliography.source.resource.publicationDateTime) : null,
|
|
1298
|
+
type: bibliography.source.resource.type,
|
|
1299
|
+
identification: parseIdentification(bibliography.source.resource.identification)
|
|
1300
|
+
};
|
|
1301
|
+
let shortCitation = null;
|
|
1302
|
+
let longCitation = null;
|
|
1303
|
+
if (bibliography.citationFormatSpan) try {
|
|
1304
|
+
shortCitation = JSON.parse(`"${bibliography.citationFormatSpan}"`).replaceAll("<", "<").replaceAll(">", ">");
|
|
1305
|
+
} catch {
|
|
1306
|
+
shortCitation = bibliography.citationFormatSpan;
|
|
1307
|
+
}
|
|
1308
|
+
if (bibliography.referenceFormatDiv) try {
|
|
1309
|
+
longCitation = JSON.parse(`"${bibliography.referenceFormatDiv}"`).replaceAll("<", "<").replaceAll(">", ">");
|
|
1310
|
+
} catch {
|
|
1311
|
+
longCitation = bibliography.referenceFormatDiv;
|
|
1312
|
+
}
|
|
1313
|
+
return {
|
|
1314
|
+
uuid: bibliography.uuid ?? null,
|
|
1315
|
+
zoteroId: bibliography.zoteroId ?? null,
|
|
1316
|
+
category: "bibliography",
|
|
1317
|
+
publicationDateTime: bibliography.publicationDateTime != null ? new Date(bibliography.publicationDateTime) : null,
|
|
1318
|
+
type: bibliography.type ?? null,
|
|
1319
|
+
number: bibliography.n ?? null,
|
|
1320
|
+
identification: bibliography.identification ? parseIdentification(bibliography.identification) : null,
|
|
1321
|
+
projectIdentification: bibliography.project?.identification ? parseIdentification(bibliography.project.identification) : null,
|
|
1322
|
+
context: bibliography.context ? parseContext(bibliography.context) : null,
|
|
1323
|
+
citation: {
|
|
1324
|
+
details: bibliography.citationDetails ?? null,
|
|
1325
|
+
format: bibliography.citationFormat ?? null,
|
|
1326
|
+
short: shortCitation,
|
|
1327
|
+
long: longCitation
|
|
1328
|
+
},
|
|
1329
|
+
publicationInfo: {
|
|
1330
|
+
publishers: bibliography.publicationInfo?.publishers ? parsePersons(Array.isArray(bibliography.publicationInfo.publishers.publishers.person) ? bibliography.publicationInfo.publishers.publishers.person : [bibliography.publicationInfo.publishers.publishers.person]) : [],
|
|
1331
|
+
startDate: bibliography.publicationInfo?.startDate ? new Date(bibliography.publicationInfo.startDate.year, bibliography.publicationInfo.startDate.month, bibliography.publicationInfo.startDate.day) : null
|
|
1332
|
+
},
|
|
1333
|
+
entryInfo: bibliography.entryInfo ? {
|
|
1334
|
+
startIssue: parseFakeString(bibliography.entryInfo.startIssue),
|
|
1335
|
+
startVolume: parseFakeString(bibliography.entryInfo.startVolume)
|
|
1336
|
+
} : null,
|
|
1337
|
+
source: {
|
|
1338
|
+
resource,
|
|
1339
|
+
documentUrl: bibliography.sourceDocument ? `https://ochre.lib.uchicago.edu/ochre?uuid=${bibliography.sourceDocument.uuid}&load` : null
|
|
1340
|
+
},
|
|
1341
|
+
periods: bibliography.periods ? parsePeriods(Array.isArray(bibliography.periods.period) ? bibliography.periods.period : [bibliography.periods.period]) : [],
|
|
1342
|
+
authors: bibliography.authors ? parsePersons(Array.isArray(bibliography.authors.person) ? bibliography.authors.person : [bibliography.authors.person]) : [],
|
|
1343
|
+
properties: bibliography.properties ? parseProperties(Array.isArray(bibliography.properties.property) ? bibliography.properties.property : [bibliography.properties.property]) : []
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Parses an array of raw bibliographies into standardized Bibliography objects
|
|
1348
|
+
*
|
|
1349
|
+
* @param bibliographies - Array of raw bibliographies in OCHRE format
|
|
1350
|
+
* @returns Array of parsed Bibliography objects
|
|
1351
|
+
*/
|
|
1352
|
+
function parseBibliographies(bibliographies) {
|
|
1353
|
+
const returnBibliographies = [];
|
|
1354
|
+
for (const bibliography of bibliographies) returnBibliographies.push(parseBibliography(bibliography));
|
|
1355
|
+
return returnBibliographies;
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Parses raw property value data into a standardized PropertyValue structure
|
|
1359
|
+
*
|
|
1360
|
+
* @param propertyValue - Raw property value data in OCHRE format
|
|
1361
|
+
* @returns Parsed PropertyValue object
|
|
1362
|
+
*/
|
|
1363
|
+
function parsePropertyValue(propertyValue) {
|
|
1364
|
+
return {
|
|
1365
|
+
uuid: propertyValue.uuid,
|
|
1366
|
+
category: "propertyValue",
|
|
1367
|
+
number: propertyValue.n,
|
|
1368
|
+
publicationDateTime: propertyValue.publicationDateTime ? new Date(propertyValue.publicationDateTime) : null,
|
|
1369
|
+
context: propertyValue.context ? parseContext(propertyValue.context) : null,
|
|
1370
|
+
availability: propertyValue.availability ? parseLicense(propertyValue.availability) : null,
|
|
1371
|
+
identification: parseIdentification(propertyValue.identification),
|
|
1372
|
+
date: propertyValue.date ?? null,
|
|
1373
|
+
creators: propertyValue.creators ? parsePersons(Array.isArray(propertyValue.creators.creator) ? propertyValue.creators.creator : [propertyValue.creators.creator]) : [],
|
|
1374
|
+
description: propertyValue.description ? typeof propertyValue.description === "string" || typeof propertyValue.description === "number" || typeof propertyValue.description === "boolean" ? parseFakeString(propertyValue.description) : parseStringContent(propertyValue.description) : "",
|
|
1375
|
+
notes: propertyValue.notes ? parseNotes(Array.isArray(propertyValue.notes.note) ? propertyValue.notes.note : [propertyValue.notes.note]) : [],
|
|
1376
|
+
links: propertyValue.links ? parseLinks(Array.isArray(propertyValue.links) ? propertyValue.links : [propertyValue.links]) : []
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Parses an array of raw property values into standardized PropertyValue objects
|
|
1381
|
+
*
|
|
1382
|
+
* @param propertyValues - Array of raw property values in OCHRE format
|
|
1383
|
+
* @returns Array of parsed PropertyValue objects
|
|
1384
|
+
*/
|
|
1385
|
+
function parsePropertyValues(propertyValues) {
|
|
1386
|
+
const returnPropertyValues = [];
|
|
1387
|
+
for (const propertyValue of propertyValues) returnPropertyValues.push(parsePropertyValue(propertyValue));
|
|
1388
|
+
return returnPropertyValues;
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Parses a raw tree structure into a standardized Tree object
|
|
1392
|
+
*
|
|
1393
|
+
* @param tree - Raw tree data in OCHRE format
|
|
1394
|
+
* @returns Parsed Tree object or null if invalid
|
|
1395
|
+
*/
|
|
1396
|
+
function parseTree(tree, itemCategory, itemSubCategory) {
|
|
1397
|
+
if (typeof tree.items === "string") throw new TypeError("Invalid OCHRE data: Tree has no items");
|
|
1398
|
+
let creators = [];
|
|
1399
|
+
if (tree.creators) creators = parsePersons(Array.isArray(tree.creators.creator) ? tree.creators.creator : [tree.creators.creator]);
|
|
1400
|
+
let date = null;
|
|
1401
|
+
if (tree.date != null) date = tree.date;
|
|
1402
|
+
const parsedItemCategory = itemSubCategory ?? getItemCategory(Object.keys(tree.items));
|
|
1403
|
+
let items = [];
|
|
1404
|
+
switch (parsedItemCategory) {
|
|
1405
|
+
case "resource":
|
|
1406
|
+
if (!("resource" in tree.items)) throw new Error("Invalid OCHRE data: Tree has no resources");
|
|
1407
|
+
items = parseResources(Array.isArray(tree.items.resource) ? tree.items.resource : [tree.items.resource]);
|
|
1408
|
+
break;
|
|
1409
|
+
case "spatialUnit":
|
|
1410
|
+
if (!("spatialUnit" in tree.items)) throw new Error("Invalid OCHRE data: Tree has no spatial units");
|
|
1411
|
+
items = parseSpatialUnits(Array.isArray(tree.items.spatialUnit) ? tree.items.spatialUnit : [tree.items.spatialUnit]);
|
|
1412
|
+
break;
|
|
1413
|
+
case "concept":
|
|
1414
|
+
if (!("concept" in tree.items)) throw new Error("Invalid OCHRE data: Tree has no concepts");
|
|
1415
|
+
items = parseConcepts(Array.isArray(tree.items.concept) ? tree.items.concept : [tree.items.concept]);
|
|
1416
|
+
break;
|
|
1417
|
+
case "period":
|
|
1418
|
+
if (!("period" in tree.items)) throw new Error("Invalid OCHRE data: Tree has no periods");
|
|
1419
|
+
items = parsePeriods(Array.isArray(tree.items.period) ? tree.items.period : [tree.items.period]);
|
|
1420
|
+
break;
|
|
1421
|
+
case "bibliography":
|
|
1422
|
+
if (!("bibliography" in tree.items)) throw new Error("Invalid OCHRE data: Tree has no bibliographies");
|
|
1423
|
+
items = parseBibliographies(Array.isArray(tree.items.bibliography) ? tree.items.bibliography : [tree.items.bibliography]);
|
|
1424
|
+
break;
|
|
1425
|
+
case "person":
|
|
1426
|
+
if (!("person" in tree.items)) throw new Error("Invalid OCHRE data: Tree has no persons");
|
|
1427
|
+
items = parsePersons(Array.isArray(tree.items.person) ? tree.items.person : [tree.items.person]);
|
|
1428
|
+
break;
|
|
1429
|
+
case "propertyValue":
|
|
1430
|
+
if (!("propertyValue" in tree.items)) throw new Error("Invalid OCHRE data: Tree has no property values");
|
|
1431
|
+
items = parsePropertyValues(Array.isArray(tree.items.propertyValue) ? tree.items.propertyValue : [tree.items.propertyValue]);
|
|
1432
|
+
break;
|
|
1433
|
+
case "set": {
|
|
1434
|
+
if (!("set" in tree.items)) throw new Error("Invalid OCHRE data: Tree has no sets");
|
|
1435
|
+
const setItems = [];
|
|
1436
|
+
for (const item of Array.isArray(tree.items.set) ? tree.items.set : [tree.items.set]) setItems.push(parseSet(item, itemSubCategory));
|
|
1437
|
+
items = setItems;
|
|
1438
|
+
break;
|
|
1439
|
+
}
|
|
1440
|
+
default: throw new Error("Invalid OCHRE data: Tree has no items or is malformed");
|
|
1441
|
+
}
|
|
1442
|
+
return {
|
|
1443
|
+
uuid: tree.uuid,
|
|
1444
|
+
category: "tree",
|
|
1445
|
+
publicationDateTime: new Date(tree.publicationDateTime),
|
|
1446
|
+
identification: parseIdentification(tree.identification),
|
|
1447
|
+
creators,
|
|
1448
|
+
license: parseLicense(tree.availability),
|
|
1449
|
+
date,
|
|
1450
|
+
type: tree.type,
|
|
1451
|
+
number: tree.n,
|
|
1452
|
+
items,
|
|
1453
|
+
properties: tree.properties ? parseProperties(Array.isArray(tree.properties.property) ? tree.properties.property : [tree.properties.property]) : []
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Parses raw set data into a standardized Set structure
|
|
1458
|
+
*
|
|
1459
|
+
* @param set - Raw set data in OCHRE format
|
|
1460
|
+
* @returns Parsed Set object
|
|
1461
|
+
*/
|
|
1462
|
+
function parseSet(set, itemCategory) {
|
|
1463
|
+
if (typeof set.items === "string") throw new TypeError("Invalid OCHRE data: Set has no items");
|
|
1464
|
+
const parsedItemCategory = itemCategory ?? getItemCategory(Object.keys(set.items));
|
|
1465
|
+
let items = [];
|
|
1466
|
+
switch (parsedItemCategory) {
|
|
1467
|
+
case "resource":
|
|
1468
|
+
if (!("resource" in set.items)) throw new Error("Invalid OCHRE data: Set has no resources");
|
|
1469
|
+
items = parseResources(Array.isArray(set.items.resource) ? set.items.resource : [set.items.resource]);
|
|
1470
|
+
break;
|
|
1471
|
+
case "spatialUnit":
|
|
1472
|
+
if (!("spatialUnit" in set.items)) throw new Error("Invalid OCHRE data: Set has no spatial units");
|
|
1473
|
+
items = parseSpatialUnits(Array.isArray(set.items.spatialUnit) ? set.items.spatialUnit : [set.items.spatialUnit]);
|
|
1474
|
+
break;
|
|
1475
|
+
case "concept":
|
|
1476
|
+
if (!("concept" in set.items)) throw new Error("Invalid OCHRE data: Set has no concepts");
|
|
1477
|
+
items = parseConcepts(Array.isArray(set.items.concept) ? set.items.concept : [set.items.concept]);
|
|
1478
|
+
break;
|
|
1479
|
+
case "period":
|
|
1480
|
+
if (!("period" in set.items)) throw new Error("Invalid OCHRE data: Set has no periods");
|
|
1481
|
+
items = parsePeriods(Array.isArray(set.items.period) ? set.items.period : [set.items.period]);
|
|
1482
|
+
break;
|
|
1483
|
+
case "bibliography":
|
|
1484
|
+
if (!("bibliography" in set.items)) throw new Error("Invalid OCHRE data: Set has no bibliographies");
|
|
1485
|
+
items = parseBibliographies(Array.isArray(set.items.bibliography) ? set.items.bibliography : [set.items.bibliography]);
|
|
1486
|
+
break;
|
|
1487
|
+
case "person":
|
|
1488
|
+
if (!("person" in set.items)) throw new Error("Invalid OCHRE data: Set has no persons");
|
|
1489
|
+
items = parsePersons(Array.isArray(set.items.person) ? set.items.person : [set.items.person]);
|
|
1490
|
+
break;
|
|
1491
|
+
case "propertyValue":
|
|
1492
|
+
if (!("propertyValue" in set.items)) throw new Error("Invalid OCHRE data: Set has no property values");
|
|
1493
|
+
items = parsePropertyValues(Array.isArray(set.items.propertyValue) ? set.items.propertyValue : [set.items.propertyValue]);
|
|
1494
|
+
break;
|
|
1495
|
+
default: throw new Error("Invalid OCHRE data: Set has no items or is malformed");
|
|
1496
|
+
}
|
|
1497
|
+
return {
|
|
1498
|
+
uuid: set.uuid,
|
|
1499
|
+
category: "set",
|
|
1500
|
+
itemCategory,
|
|
1501
|
+
publicationDateTime: set.publicationDateTime ? new Date(set.publicationDateTime) : null,
|
|
1502
|
+
date: set.date ?? null,
|
|
1503
|
+
license: parseLicense(set.availability),
|
|
1504
|
+
identification: parseIdentification(set.identification),
|
|
1505
|
+
isSuppressingBlanks: set.suppressBlanks ?? false,
|
|
1506
|
+
description: set.description ? [
|
|
1507
|
+
"string",
|
|
1508
|
+
"number",
|
|
1509
|
+
"boolean"
|
|
1510
|
+
].includes(typeof set.description) ? parseFakeString(set.description) : parseStringContent(set.description) : "",
|
|
1511
|
+
creators: set.creators ? parsePersons(Array.isArray(set.creators.creator) ? set.creators.creator : [set.creators.creator]) : [],
|
|
1512
|
+
type: set.type,
|
|
1513
|
+
number: set.n,
|
|
1514
|
+
items
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Parses raw resource data into a standardized Resource structure
|
|
1519
|
+
*
|
|
1520
|
+
* @param resource - Raw resource data in OCHRE format
|
|
1521
|
+
* @returns Parsed Resource object
|
|
1522
|
+
*/
|
|
1523
|
+
function parseResource(resource) {
|
|
1524
|
+
return {
|
|
1525
|
+
uuid: resource.uuid,
|
|
1526
|
+
category: "resource",
|
|
1527
|
+
publicationDateTime: resource.publicationDateTime ? new Date(resource.publicationDateTime) : null,
|
|
1528
|
+
type: resource.type,
|
|
1529
|
+
number: resource.n,
|
|
1530
|
+
fileFormat: resource.fileFormat ?? null,
|
|
1531
|
+
fileSize: resource.fileSize ?? null,
|
|
1532
|
+
context: "context" in resource && resource.context ? parseContext(resource.context) : null,
|
|
1533
|
+
license: "availability" in resource && resource.availability ? parseLicense(resource.availability) : null,
|
|
1534
|
+
copyright: "copyright" in resource && resource.copyright != null ? parseStringContent(resource.copyright) : null,
|
|
1535
|
+
watermark: "watermark" in resource && resource.watermark != null ? parseStringContent(resource.watermark) : null,
|
|
1536
|
+
identification: parseIdentification(resource.identification),
|
|
1537
|
+
date: resource.date ?? null,
|
|
1538
|
+
image: resource.image ? parseImage(resource.image) : null,
|
|
1539
|
+
creators: resource.creators ? parsePersons(Array.isArray(resource.creators.creator) ? resource.creators.creator : [resource.creators.creator]) : [],
|
|
1540
|
+
notes: resource.notes ? parseNotes(Array.isArray(resource.notes.note) ? resource.notes.note : [resource.notes.note]) : [],
|
|
1541
|
+
description: resource.description ? [
|
|
1542
|
+
"string",
|
|
1543
|
+
"number",
|
|
1544
|
+
"boolean"
|
|
1545
|
+
].includes(typeof resource.description) ? parseFakeString(resource.description) : parseStringContent(resource.description) : "",
|
|
1546
|
+
coordinates: resource.coordinates ? parseCoordinates(resource.coordinates) : [],
|
|
1547
|
+
document: resource.document && "content" in resource.document ? parseDocument(resource.document.content) : null,
|
|
1548
|
+
href: resource.href ?? null,
|
|
1549
|
+
imageMap: resource.imagemap ? parseImageMap(resource.imagemap) : null,
|
|
1550
|
+
periods: resource.periods ? parsePeriods(Array.isArray(resource.periods.period) ? resource.periods.period : [resource.periods.period]) : [],
|
|
1551
|
+
links: resource.links ? parseLinks(Array.isArray(resource.links) ? resource.links : [resource.links]) : [],
|
|
1552
|
+
reverseLinks: resource.reverseLinks ? parseLinks(Array.isArray(resource.reverseLinks) ? resource.reverseLinks : [resource.reverseLinks]) : [],
|
|
1553
|
+
properties: resource.properties ? parseProperties(Array.isArray(resource.properties.property) ? resource.properties.property : [resource.properties.property]) : [],
|
|
1554
|
+
bibliographies: resource.bibliographies ? parseBibliographies(Array.isArray(resource.bibliographies.bibliography) ? resource.bibliographies.bibliography : [resource.bibliographies.bibliography]) : [],
|
|
1555
|
+
resources: resource.resource ? parseResources(Array.isArray(resource.resource) ? resource.resource : [resource.resource]) : []
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Parses raw resource data into a standardized Resource structure
|
|
1560
|
+
*
|
|
1561
|
+
* @param resources - Raw resource data in OCHRE format
|
|
1562
|
+
* @returns Parsed Resource object
|
|
1563
|
+
*/
|
|
1564
|
+
function parseResources(resources) {
|
|
1565
|
+
const returnResources = [];
|
|
1566
|
+
const resourcesToParse = Array.isArray(resources) ? resources : [resources];
|
|
1567
|
+
for (const resource of resourcesToParse) returnResources.push(parseResource(resource));
|
|
1568
|
+
return returnResources;
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Parses raw spatial units into standardized SpatialUnit objects
|
|
1572
|
+
*
|
|
1573
|
+
* @param spatialUnit - Raw spatial unit in OCHRE format
|
|
1574
|
+
* @returns Parsed SpatialUnit object
|
|
1575
|
+
*/
|
|
1576
|
+
function parseSpatialUnit(spatialUnit) {
|
|
1577
|
+
return {
|
|
1578
|
+
uuid: spatialUnit.uuid,
|
|
1579
|
+
category: "spatialUnit",
|
|
1580
|
+
publicationDateTime: spatialUnit.publicationDateTime != null ? new Date(spatialUnit.publicationDateTime) : null,
|
|
1581
|
+
number: spatialUnit.n,
|
|
1582
|
+
context: "context" in spatialUnit && spatialUnit.context ? parseContext(spatialUnit.context) : null,
|
|
1583
|
+
license: "availability" in spatialUnit && spatialUnit.availability ? parseLicense(spatialUnit.availability) : null,
|
|
1584
|
+
identification: parseIdentification(spatialUnit.identification),
|
|
1585
|
+
image: spatialUnit.image ? parseImage(spatialUnit.image) : null,
|
|
1586
|
+
description: spatialUnit.description ? [
|
|
1587
|
+
"string",
|
|
1588
|
+
"number",
|
|
1589
|
+
"boolean"
|
|
1590
|
+
].includes(typeof spatialUnit.description) ? parseFakeString(spatialUnit.description) : parseStringContent(spatialUnit.description) : "",
|
|
1591
|
+
coordinates: parseCoordinates(spatialUnit.coordinates),
|
|
1592
|
+
mapData: spatialUnit.mapData ?? null,
|
|
1593
|
+
observations: "observations" in spatialUnit && spatialUnit.observations ? parseObservations(Array.isArray(spatialUnit.observations.observation) ? spatialUnit.observations.observation : [spatialUnit.observations.observation]) : spatialUnit.observation ? [parseObservation(spatialUnit.observation)] : [],
|
|
1594
|
+
events: "events" in spatialUnit && spatialUnit.events ? parseEvents(Array.isArray(spatialUnit.events.event) ? spatialUnit.events.event : [spatialUnit.events.event]) : [],
|
|
1595
|
+
properties: "properties" in spatialUnit && spatialUnit.properties ? parseProperties(Array.isArray(spatialUnit.properties.property) ? spatialUnit.properties.property : [spatialUnit.properties.property]) : [],
|
|
1596
|
+
bibliographies: spatialUnit.bibliographies ? parseBibliographies(Array.isArray(spatialUnit.bibliographies.bibliography) ? spatialUnit.bibliographies.bibliography : [spatialUnit.bibliographies.bibliography]) : []
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Parses an array of raw spatial units into standardized SpatialUnit objects
|
|
1601
|
+
*
|
|
1602
|
+
* @param spatialUnits - Array of raw spatial units in OCHRE format
|
|
1603
|
+
* @returns Array of parsed SpatialUnit objects
|
|
1604
|
+
*/
|
|
1605
|
+
function parseSpatialUnits(spatialUnits) {
|
|
1606
|
+
const returnSpatialUnits = [];
|
|
1607
|
+
const spatialUnitsToParse = Array.isArray(spatialUnits) ? spatialUnits : [spatialUnits];
|
|
1608
|
+
for (const spatialUnit of spatialUnitsToParse) returnSpatialUnits.push(parseSpatialUnit(spatialUnit));
|
|
1609
|
+
return returnSpatialUnits;
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Parses a raw concept into a standardized Concept object
|
|
1613
|
+
*
|
|
1614
|
+
* @param concept - Raw concept data in OCHRE format
|
|
1615
|
+
* @returns Parsed Concept object
|
|
1616
|
+
*/
|
|
1617
|
+
function parseConcept(concept) {
|
|
1618
|
+
return {
|
|
1619
|
+
uuid: concept.uuid,
|
|
1620
|
+
category: "concept",
|
|
1621
|
+
publicationDateTime: concept.publicationDateTime ? new Date(concept.publicationDateTime) : null,
|
|
1622
|
+
number: concept.n,
|
|
1623
|
+
license: "availability" in concept && concept.availability ? parseLicense(concept.availability) : null,
|
|
1624
|
+
context: "context" in concept && concept.context ? parseContext(concept.context) : null,
|
|
1625
|
+
identification: parseIdentification(concept.identification),
|
|
1626
|
+
image: concept.image ? parseImage(concept.image) : null,
|
|
1627
|
+
description: concept.description ? parseStringContent(concept.description) : null,
|
|
1628
|
+
interpretations: concept.interpretations ? parseInterpretations(Array.isArray(concept.interpretations.interpretation) ? concept.interpretations.interpretation : [concept.interpretations.interpretation]) : [],
|
|
1629
|
+
properties: concept.properties ? parseProperties(Array.isArray(concept.properties.property) ? concept.properties.property : [concept.properties.property]) : [],
|
|
1630
|
+
bibliographies: concept.bibliographies ? parseBibliographies(Array.isArray(concept.bibliographies.bibliography) ? concept.bibliographies.bibliography : [concept.bibliographies.bibliography]) : []
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Parses raw webpage resources into standardized WebElement or Webpage objects
|
|
1635
|
+
*
|
|
1636
|
+
* @param webpageResources - Array of raw webpage resources in OCHRE format
|
|
1637
|
+
* @param type - Type of resource to parse ("element" or "page")
|
|
1638
|
+
* @returns Array of parsed WebElement or Webpage objects
|
|
1639
|
+
*/
|
|
1640
|
+
const parseWebpageResources = (webpageResources, type) => {
|
|
1641
|
+
const returnElements = [];
|
|
1642
|
+
for (const resource of webpageResources) {
|
|
1643
|
+
if (!(resource.properties ? parseProperties(Array.isArray(resource.properties.property) ? resource.properties.property : [resource.properties.property]) : []).find((property) => property.label === "presentation" && property.values[0].content === type)) continue;
|
|
1644
|
+
switch (type) {
|
|
1645
|
+
case "element": {
|
|
1646
|
+
const element = parseWebElement(resource);
|
|
1647
|
+
returnElements.push(element);
|
|
1648
|
+
break;
|
|
1649
|
+
}
|
|
1650
|
+
case "page": {
|
|
1651
|
+
const webpage = parseWebpage(resource);
|
|
1652
|
+
if (webpage) returnElements.push(webpage);
|
|
1653
|
+
break;
|
|
1654
|
+
}
|
|
1655
|
+
case "block": {
|
|
1656
|
+
const block = parseWebBlock(resource);
|
|
1657
|
+
if (block) returnElements.push(block);
|
|
1658
|
+
break;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
return returnElements;
|
|
1663
|
+
};
|
|
1664
|
+
/**
|
|
1665
|
+
* Parses raw concept data into standardized Concept objects
|
|
1666
|
+
*
|
|
1667
|
+
* @param concepts - Array of raw concept data in OCHRE format
|
|
1668
|
+
* @returns Array of parsed Concept objects
|
|
1669
|
+
*/
|
|
1670
|
+
function parseConcepts(concepts) {
|
|
1671
|
+
const returnConcepts = [];
|
|
1672
|
+
const conceptsToParse = Array.isArray(concepts) ? concepts : [concepts];
|
|
1673
|
+
for (const concept of conceptsToParse) returnConcepts.push(parseConcept(concept));
|
|
1674
|
+
return returnConcepts;
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Parses raw web element properties into a standardized WebElementComponent structure
|
|
1678
|
+
*
|
|
1679
|
+
* @param componentProperty - Raw component property data in OCHRE format
|
|
1680
|
+
* @param elementResource - Raw element resource data in OCHRE format
|
|
1681
|
+
* @returns Parsed WebElementComponent object
|
|
1682
|
+
*/
|
|
1683
|
+
function parseWebElementProperties(componentProperty, elementResource) {
|
|
1684
|
+
const unparsedComponentName = componentProperty.values[0].content;
|
|
1685
|
+
const { data: componentName } = componentSchema.safeParse(unparsedComponentName);
|
|
1686
|
+
const properties = { component: componentName };
|
|
1687
|
+
const links = elementResource.links ? parseLinks(Array.isArray(elementResource.links) ? elementResource.links : [elementResource.links]) : [];
|
|
1688
|
+
switch (componentName) {
|
|
1689
|
+
case "annotated-document": {
|
|
1690
|
+
const documentLink = links.find((link) => link.type === "internalDocument");
|
|
1691
|
+
if (!documentLink) throw new Error(`Document link not found for the following component: “${componentName}”`);
|
|
1692
|
+
properties.documentId = documentLink.uuid;
|
|
1693
|
+
break;
|
|
1694
|
+
}
|
|
1695
|
+
case "annotated-image": {
|
|
1696
|
+
const imageLinks = links.filter((link) => link.type === "image" || link.type === "IIIF");
|
|
1697
|
+
if (imageLinks.length === 0) throw new Error(`Image link not found for the following component: “${componentName}”`);
|
|
1698
|
+
const isFilterDisplayed = getPropertyValueByLabel(componentProperty.properties, "filter-displayed") === true;
|
|
1699
|
+
const isOptionsDisplayed = getPropertyValueByLabel(componentProperty.properties, "options-displayed") !== false;
|
|
1700
|
+
const isAnnotationHighlightsDisplayed = getPropertyValueByLabel(componentProperty.properties, "annotation-highlights-displayed") !== false;
|
|
1701
|
+
const isAnnotationTooltipsDisplayed = getPropertyValueByLabel(componentProperty.properties, "annotation-tooltips-displayed") !== false;
|
|
1702
|
+
properties.imageUuid = imageLinks[0].uuid;
|
|
1703
|
+
properties.isFilterDisplayed = isFilterDisplayed;
|
|
1704
|
+
properties.isOptionsDisplayed = isOptionsDisplayed;
|
|
1705
|
+
properties.isAnnotationHighlightsDisplayed = isAnnotationHighlightsDisplayed;
|
|
1706
|
+
properties.isAnnotationTooltipsDisplayed = isAnnotationTooltipsDisplayed;
|
|
1707
|
+
break;
|
|
1708
|
+
}
|
|
1709
|
+
case "audio-player": {
|
|
1710
|
+
const audioLink = links.find((link) => link.type === "audio");
|
|
1711
|
+
if (!audioLink) throw new Error(`Audio link not found for the following component: “${componentName}”`);
|
|
1712
|
+
let isSpeedControlsDisplayed = false;
|
|
1713
|
+
const isSpeedControlsDisplayedProperty = getPropertyValueByLabel(componentProperty.properties, "speed-controls-displayed");
|
|
1714
|
+
if (isSpeedControlsDisplayedProperty !== null) isSpeedControlsDisplayed = isSpeedControlsDisplayedProperty === true;
|
|
1715
|
+
let isVolumeControlsDisplayed = false;
|
|
1716
|
+
const isVolumeControlsDisplayedProperty = getPropertyValueByLabel(componentProperty.properties, "volume-controls-displayed");
|
|
1717
|
+
if (isVolumeControlsDisplayedProperty !== null) isVolumeControlsDisplayed = isVolumeControlsDisplayedProperty === true;
|
|
1718
|
+
let isSeekBarDisplayed = false;
|
|
1719
|
+
const isSeekBarDisplayedProperty = getPropertyValueByLabel(componentProperty.properties, "seek-bar-displayed");
|
|
1720
|
+
if (isSeekBarDisplayedProperty !== null) isSeekBarDisplayed = isSeekBarDisplayedProperty === true;
|
|
1721
|
+
properties.audioId = audioLink.uuid;
|
|
1722
|
+
properties.isSpeedControlsDisplayed = isSpeedControlsDisplayed;
|
|
1723
|
+
properties.isVolumeControlsDisplayed = isVolumeControlsDisplayed;
|
|
1724
|
+
properties.isSeekBarDisplayed = isSeekBarDisplayed;
|
|
1725
|
+
break;
|
|
1726
|
+
}
|
|
1727
|
+
case "bibliography": {
|
|
1728
|
+
const itemLinks = links.filter((link) => link.category !== "bibliography");
|
|
1729
|
+
const bibliographyLink = links.find((link) => link.category === "bibliography");
|
|
1730
|
+
if (itemLinks.length === 0 && bibliographyLink?.bibliographies == null) throw new Error(`No links found for the following component: “${componentName}”`);
|
|
1731
|
+
let layout = getPropertyValueByLabel(componentProperty.properties, "layout");
|
|
1732
|
+
layout ??= "long";
|
|
1733
|
+
let isSourceDocumentDisplayed = true;
|
|
1734
|
+
const isSourceDocumentDisplayedProperty = getPropertyValueByLabel(componentProperty.properties, "source-document-displayed");
|
|
1735
|
+
if (isSourceDocumentDisplayedProperty !== null) isSourceDocumentDisplayed = isSourceDocumentDisplayedProperty === true;
|
|
1736
|
+
properties.itemUuids = itemLinks.map((link) => link.uuid).filter((uuid) => uuid !== null);
|
|
1737
|
+
properties.bibliographies = bibliographyLink?.bibliographies ?? [];
|
|
1738
|
+
properties.layout = layout;
|
|
1739
|
+
properties.isSourceDocumentDisplayed = isSourceDocumentDisplayed;
|
|
1740
|
+
break;
|
|
1741
|
+
}
|
|
1742
|
+
case "button": {
|
|
1743
|
+
let variant = getPropertyValueByLabel(componentProperty.properties, "variant");
|
|
1744
|
+
variant ??= "default";
|
|
1745
|
+
let isExternal = false;
|
|
1746
|
+
const navigateToProperty = getPropertyByLabel(componentProperty.properties, "navigate-to");
|
|
1747
|
+
let href = navigateToProperty?.values[0]?.href ?? navigateToProperty?.values[0]?.slug ?? null;
|
|
1748
|
+
if (href === null) {
|
|
1749
|
+
const linkToProperty = getPropertyByLabel(componentProperty.properties, "link-to");
|
|
1750
|
+
href = linkToProperty?.values[0]?.href ?? linkToProperty?.values[0]?.slug ?? null;
|
|
1751
|
+
if (href === null) throw new Error(`Properties “navigate-to” or “link-to” not found for the following component: “${componentName}”`);
|
|
1752
|
+
else isExternal = true;
|
|
1753
|
+
}
|
|
1754
|
+
let startIcon = null;
|
|
1755
|
+
const startIconProperty = getPropertyValueByLabel(componentProperty.properties, "start-icon");
|
|
1756
|
+
if (startIconProperty !== null) startIcon = startIconProperty;
|
|
1757
|
+
let endIcon = null;
|
|
1758
|
+
const endIconProperty = getPropertyValueByLabel(componentProperty.properties, "end-icon");
|
|
1759
|
+
if (endIconProperty !== null) endIcon = endIconProperty;
|
|
1760
|
+
let image = null;
|
|
1761
|
+
const imageLink = links.find((link) => link.type === "image" || link.type === "IIIF");
|
|
1762
|
+
if (imageLink != null) image = {
|
|
1763
|
+
url: `https://ochre.lib.uchicago.edu/ochre?uuid=${imageLink.uuid}&load`,
|
|
1764
|
+
label: imageLink.identification?.label ?? null,
|
|
1765
|
+
width: imageLink.image?.width ?? 0,
|
|
1766
|
+
height: imageLink.image?.height ?? 0,
|
|
1767
|
+
description: imageLink.description ?? null
|
|
1768
|
+
};
|
|
1769
|
+
properties.variant = variant;
|
|
1770
|
+
properties.href = href;
|
|
1771
|
+
properties.isExternal = isExternal;
|
|
1772
|
+
properties.label = elementResource.document && "content" in elementResource.document ? parseDocument(elementResource.document.content) : null;
|
|
1773
|
+
properties.startIcon = startIcon;
|
|
1774
|
+
properties.endIcon = endIcon;
|
|
1775
|
+
properties.image = image;
|
|
1776
|
+
break;
|
|
1777
|
+
}
|
|
1778
|
+
case "collection": {
|
|
1779
|
+
const collectionLink = links.find((link) => link.category === "set");
|
|
1780
|
+
if (!collectionLink) throw new Error(`Collection link not found for the following component: “${componentName}”`);
|
|
1781
|
+
let variant = getPropertyValueByLabel(componentProperty.properties, "variant");
|
|
1782
|
+
variant ??= "full";
|
|
1783
|
+
let itemVariant = getPropertyValueByLabel(componentProperty.properties, "item-variant");
|
|
1784
|
+
itemVariant ??= "detailed";
|
|
1785
|
+
let paginationVariant = getPropertyValueByLabel(componentProperty.properties, "pagination-variant");
|
|
1786
|
+
paginationVariant ??= "default";
|
|
1787
|
+
let isSortDisplayed = false;
|
|
1788
|
+
const isSortDisplayedProperty = getPropertyValueByLabel(componentProperty.properties, "sort-displayed");
|
|
1789
|
+
if (isSortDisplayedProperty !== null) isSortDisplayed = isSortDisplayedProperty === true;
|
|
1790
|
+
let isFilterDisplayed = false;
|
|
1791
|
+
const isFilterDisplayedProperty = getPropertyValueByLabel(componentProperty.properties, "filter-displayed");
|
|
1792
|
+
if (isFilterDisplayedProperty !== null) isFilterDisplayed = isFilterDisplayedProperty === true;
|
|
1793
|
+
let filterSort = getPropertyValueByLabel(componentProperty.properties, "filter-sort");
|
|
1794
|
+
filterSort ??= "default";
|
|
1795
|
+
let layout = getPropertyValueByLabel(componentProperty.properties, "layout");
|
|
1796
|
+
layout ??= "image-start";
|
|
1797
|
+
properties.collectionId = collectionLink.uuid;
|
|
1798
|
+
properties.variant = variant;
|
|
1799
|
+
properties.itemVariant = itemVariant;
|
|
1800
|
+
properties.paginationVariant = paginationVariant;
|
|
1801
|
+
properties.isSortDisplayed = isSortDisplayed;
|
|
1802
|
+
properties.isFilterDisplayed = isFilterDisplayed;
|
|
1803
|
+
properties.filterSort = filterSort;
|
|
1804
|
+
properties.layout = layout;
|
|
1805
|
+
break;
|
|
1806
|
+
}
|
|
1807
|
+
case "empty-space": {
|
|
1808
|
+
const height = getPropertyValueByLabel(componentProperty.properties, "height");
|
|
1809
|
+
const width = getPropertyValueByLabel(componentProperty.properties, "width");
|
|
1810
|
+
properties.height = height;
|
|
1811
|
+
properties.width = width;
|
|
1812
|
+
break;
|
|
1813
|
+
}
|
|
1814
|
+
case "entries": {
|
|
1815
|
+
const entriesLink = links.find((link) => link.category === "tree" || link.category === "set");
|
|
1816
|
+
if (!entriesLink) throw new Error(`Entries link not found for the following component: “${componentName}”`);
|
|
1817
|
+
let variant = getPropertyValueByLabel(componentProperty.properties, "variant");
|
|
1818
|
+
variant ??= "entry";
|
|
1819
|
+
let isFilterDisplayed = false;
|
|
1820
|
+
const isFilterDisplayedProperty = getPropertyValueByLabel(componentProperty.properties, "filter-displayed");
|
|
1821
|
+
if (isFilterDisplayedProperty !== null) isFilterDisplayed = isFilterDisplayedProperty === true;
|
|
1822
|
+
properties.entriesId = entriesLink.uuid;
|
|
1823
|
+
properties.variant = variant;
|
|
1824
|
+
properties.isFilterDisplayed = isFilterDisplayed;
|
|
1825
|
+
break;
|
|
1826
|
+
}
|
|
1827
|
+
case "iframe": {
|
|
1828
|
+
const href = links.find((link) => link.type === "webpage")?.href;
|
|
1829
|
+
if (!href) throw new Error(`URL not found for the following component: “${componentName}”`);
|
|
1830
|
+
const height = getPropertyValueByLabel(componentProperty.properties, "height");
|
|
1831
|
+
const width = getPropertyValueByLabel(componentProperty.properties, "width");
|
|
1832
|
+
properties.href = href;
|
|
1833
|
+
properties.height = height;
|
|
1834
|
+
properties.width = width;
|
|
1835
|
+
break;
|
|
1836
|
+
}
|
|
1837
|
+
case "iiif-viewer": {
|
|
1838
|
+
const manifestLink = links.find((link) => link.type === "IIIF");
|
|
1839
|
+
if (!manifestLink) throw new Error(`Manifest link not found for the following component: “${componentName}”`);
|
|
1840
|
+
let variant = getPropertyValueByLabel(componentProperty.properties, "variant");
|
|
1841
|
+
variant ??= "universal-viewer";
|
|
1842
|
+
properties.iiifId = manifestLink.uuid;
|
|
1843
|
+
properties.variant = variant;
|
|
1844
|
+
break;
|
|
1845
|
+
}
|
|
1846
|
+
case "image": {
|
|
1847
|
+
if (links.length === 0) throw new Error(`No links found for the following component: “${componentName}”`);
|
|
1848
|
+
let imageQuality = getPropertyValueByLabel(componentProperty.properties, "quality");
|
|
1849
|
+
imageQuality ??= "high";
|
|
1850
|
+
const images = [];
|
|
1851
|
+
for (const link of links) images.push({
|
|
1852
|
+
url: `https://ochre.lib.uchicago.edu/ochre?uuid=${link.uuid}${imageQuality === "high" && (link.type === "image" || link.type === "IIIF") ? "&load" : "&preview"}`,
|
|
1853
|
+
label: link.identification?.label ?? null,
|
|
1854
|
+
width: link.image?.width ?? 0,
|
|
1855
|
+
height: link.image?.height ?? 0,
|
|
1856
|
+
description: link.description ?? null
|
|
1857
|
+
});
|
|
1858
|
+
let variant = getPropertyValueByLabel(componentProperty.properties, "variant");
|
|
1859
|
+
variant ??= "default";
|
|
1860
|
+
let captionLayout = getPropertyValueByLabel(componentProperty.properties, "layout-caption");
|
|
1861
|
+
captionLayout ??= "bottom";
|
|
1862
|
+
let width = null;
|
|
1863
|
+
const widthProperty = getPropertyValueByLabel(componentProperty.properties, "width");
|
|
1864
|
+
if (widthProperty !== null) {
|
|
1865
|
+
if (typeof widthProperty === "number") width = widthProperty;
|
|
1866
|
+
else if (typeof widthProperty === "string") width = Number.parseFloat(widthProperty);
|
|
1867
|
+
}
|
|
1868
|
+
let height = null;
|
|
1869
|
+
const heightProperty = getPropertyValueByLabel(componentProperty.properties, "height");
|
|
1870
|
+
if (heightProperty !== null) {
|
|
1871
|
+
if (typeof heightProperty === "number") height = heightProperty;
|
|
1872
|
+
else if (typeof heightProperty === "string") height = Number.parseFloat(heightProperty);
|
|
1873
|
+
}
|
|
1874
|
+
let isFullWidth = true;
|
|
1875
|
+
const isFullWidthProperty = getPropertyValueByLabel(componentProperty.properties, "is-full-width");
|
|
1876
|
+
if (isFullWidthProperty !== null) isFullWidth = isFullWidthProperty === true;
|
|
1877
|
+
let isFullHeight = true;
|
|
1878
|
+
const isFullHeightProperty = getPropertyValueByLabel(componentProperty.properties, "is-full-height");
|
|
1879
|
+
if (isFullHeightProperty !== null) isFullHeight = isFullHeightProperty === true;
|
|
1880
|
+
let captionSource = getPropertyValueByLabel(componentProperty.properties, "caption-source");
|
|
1881
|
+
captionSource ??= "name";
|
|
1882
|
+
let altTextSource = getPropertyValueByLabel(componentProperty.properties, "alt-text-source");
|
|
1883
|
+
altTextSource ??= "name";
|
|
1884
|
+
let isTransparentBackground = false;
|
|
1885
|
+
const isTransparentBackgroundProperty = getPropertyValueByLabel(componentProperty.properties, "is-transparent");
|
|
1886
|
+
if (isTransparentBackgroundProperty !== null) isTransparentBackground = isTransparentBackgroundProperty === true;
|
|
1887
|
+
let isCover = false;
|
|
1888
|
+
const isCoverProperty = getPropertyValueByLabel(componentProperty.properties, "is-cover");
|
|
1889
|
+
if (isCoverProperty !== null) isCover = isCoverProperty === true;
|
|
1890
|
+
const variantProperty = getPropertyByLabel(componentProperty.properties, "variant");
|
|
1891
|
+
let carouselOptions = null;
|
|
1892
|
+
if (images.length > 1) {
|
|
1893
|
+
let secondsPerImage = 5;
|
|
1894
|
+
if (variantProperty?.values[0].content === "carousel") {
|
|
1895
|
+
const secondsPerImageProperty = getPropertyValueByLabel(variantProperty.properties, "seconds-per-image");
|
|
1896
|
+
if (secondsPerImageProperty !== null) {
|
|
1897
|
+
if (typeof secondsPerImageProperty === "number") secondsPerImage = secondsPerImageProperty;
|
|
1898
|
+
else if (typeof secondsPerImageProperty === "string") secondsPerImage = Number.parseFloat(secondsPerImageProperty);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
carouselOptions = { secondsPerImage };
|
|
1902
|
+
}
|
|
1903
|
+
let heroOptions = null;
|
|
1904
|
+
if (variantProperty?.values[0].content === "hero") {
|
|
1905
|
+
const isBackgroundImageDisplayedProperty = getPropertyValueByLabel(variantProperty.properties, "background-image-displayed");
|
|
1906
|
+
const isDocumentDisplayedProperty = getPropertyValueByLabel(variantProperty.properties, "document-displayed");
|
|
1907
|
+
const isLinkDisplayedProperty = getPropertyValueByLabel(variantProperty.properties, "link-displayed");
|
|
1908
|
+
heroOptions = {
|
|
1909
|
+
isBackgroundImageDisplayed: isBackgroundImageDisplayedProperty !== false,
|
|
1910
|
+
isDocumentDisplayed: isDocumentDisplayedProperty !== false,
|
|
1911
|
+
isLinkDisplayed: isLinkDisplayedProperty !== false
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
properties.images = images;
|
|
1915
|
+
properties.variant = variant;
|
|
1916
|
+
properties.width = width;
|
|
1917
|
+
properties.height = height;
|
|
1918
|
+
properties.isFullWidth = isFullWidth;
|
|
1919
|
+
properties.isFullHeight = isFullHeight;
|
|
1920
|
+
properties.imageQuality = imageQuality;
|
|
1921
|
+
properties.captionLayout = captionLayout;
|
|
1922
|
+
properties.captionSource = captionSource;
|
|
1923
|
+
properties.altTextSource = altTextSource;
|
|
1924
|
+
properties.isTransparentBackground = isTransparentBackground;
|
|
1925
|
+
properties.isCover = isCover;
|
|
1926
|
+
properties.carouselOptions = carouselOptions;
|
|
1927
|
+
properties.heroOptions = heroOptions;
|
|
1928
|
+
break;
|
|
1929
|
+
}
|
|
1930
|
+
case "image-gallery": {
|
|
1931
|
+
const galleryLink = links.find((link) => link.category === "tree" || link.category === "set");
|
|
1932
|
+
if (!galleryLink) throw new Error(`Image gallery link not found for the following component: “${componentName}”`);
|
|
1933
|
+
const isFilterDisplayed = getPropertyValueByLabel(componentProperty.properties, "filter-displayed") === true;
|
|
1934
|
+
properties.galleryId = galleryLink.uuid;
|
|
1935
|
+
properties.isFilterDisplayed = isFilterDisplayed;
|
|
1936
|
+
break;
|
|
1937
|
+
}
|
|
1938
|
+
case "map": {
|
|
1939
|
+
const mapLink = links.find((link) => link.category === "set" || link.category === "tree");
|
|
1940
|
+
if (!mapLink) throw new Error(`Map link not found for the following component: “${componentName}”`);
|
|
1941
|
+
let isInteractive = true;
|
|
1942
|
+
const isInteractiveProperty = getPropertyValueByLabel(componentProperty.properties, "is-interactive");
|
|
1943
|
+
if (isInteractiveProperty !== null) isInteractive = isInteractiveProperty === true;
|
|
1944
|
+
let isClustered = false;
|
|
1945
|
+
const isClusteredProperty = getPropertyValueByLabel(componentProperty.properties, "is-clustered");
|
|
1946
|
+
if (isClusteredProperty !== null) isClustered = isClusteredProperty === true;
|
|
1947
|
+
let isUsingPins = false;
|
|
1948
|
+
const isUsingPinsProperty = getPropertyValueByLabel(componentProperty.properties, "is-using-pins");
|
|
1949
|
+
if (isUsingPinsProperty !== null) isUsingPins = isUsingPinsProperty === true;
|
|
1950
|
+
let customBasemap = null;
|
|
1951
|
+
const customBasemapProperty = getPropertyValueByLabel(componentProperty.properties, "custom-basemap");
|
|
1952
|
+
if (customBasemapProperty !== null) customBasemap = customBasemapProperty;
|
|
1953
|
+
let isControlsDisplayed = false;
|
|
1954
|
+
const isControlsDisplayedProperty = getPropertyValueByLabel(componentProperty.properties, "controls-displayed");
|
|
1955
|
+
if (isControlsDisplayedProperty !== null) isControlsDisplayed = isControlsDisplayedProperty === true;
|
|
1956
|
+
let isFullHeight = false;
|
|
1957
|
+
const isFullHeightProperty = getPropertyValueByLabel(componentProperty.properties, "is-full-height");
|
|
1958
|
+
if (isFullHeightProperty !== null) isFullHeight = isFullHeightProperty === true;
|
|
1959
|
+
properties.mapId = mapLink.uuid;
|
|
1960
|
+
properties.isInteractive = isInteractive;
|
|
1961
|
+
properties.isClustered = isClustered;
|
|
1962
|
+
properties.isUsingPins = isUsingPins;
|
|
1963
|
+
properties.customBasemap = customBasemap;
|
|
1964
|
+
properties.isControlsDisplayed = isControlsDisplayed;
|
|
1965
|
+
properties.isFullHeight = isFullHeight;
|
|
1966
|
+
break;
|
|
1967
|
+
}
|
|
1968
|
+
case "network-graph": break;
|
|
1969
|
+
case "query": {
|
|
1970
|
+
const queries = [];
|
|
1971
|
+
const queryProperties = componentProperty.properties;
|
|
1972
|
+
if (queryProperties.length === 0) throw new Error(`Query properties not found for the following component: “${componentName}”`);
|
|
1973
|
+
for (const query of queryProperties) {
|
|
1974
|
+
const querySubProperties = query.properties;
|
|
1975
|
+
const label = getPropertyValueByLabel(querySubProperties, "query-prompt");
|
|
1976
|
+
if (label === null) throw new Error(`Query prompt not found for the following component: “${componentName}”`);
|
|
1977
|
+
const propertyUuids = querySubProperties.find((property) => property.label === "use-property")?.values.map((value) => value.uuid).filter((uuid) => uuid !== null) ?? [];
|
|
1978
|
+
const startIcon = getPropertyValueByLabel(querySubProperties, "start-icon");
|
|
1979
|
+
const endIcon = getPropertyValueByLabel(querySubProperties, "end-icon");
|
|
1980
|
+
queries.push({
|
|
1981
|
+
label: String(label),
|
|
1982
|
+
propertyUuids,
|
|
1983
|
+
startIcon: startIcon !== null ? String(startIcon) : null,
|
|
1984
|
+
endIcon: endIcon !== null ? String(endIcon) : null
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
properties.queries = queries;
|
|
1988
|
+
break;
|
|
1989
|
+
}
|
|
1990
|
+
case "table": {
|
|
1991
|
+
const tableLink = links.find((link) => link.category === "set");
|
|
1992
|
+
if (!tableLink) throw new Error(`Table link not found for the following component: “${componentName}”`);
|
|
1993
|
+
properties.tableId = tableLink.uuid;
|
|
1994
|
+
break;
|
|
1995
|
+
}
|
|
1996
|
+
case "search-bar": {
|
|
1997
|
+
let variant = getPropertyValueByLabel(componentProperty.properties, "variant");
|
|
1998
|
+
variant ??= "default";
|
|
1999
|
+
const placeholder = getPropertyValueByLabel(componentProperty.properties, "placeholder-text");
|
|
2000
|
+
const baseQuery = getPropertyValueByLabel(componentProperty.properties, "base-query");
|
|
2001
|
+
properties.variant = variant;
|
|
2002
|
+
properties.placeholder = placeholder !== null ? String(placeholder) : null;
|
|
2003
|
+
properties.baseQuery = baseQuery !== null ? String(baseQuery).replaceAll(String.raw`\{`, "{").replaceAll(String.raw`\}`, "}") : null;
|
|
2004
|
+
break;
|
|
2005
|
+
}
|
|
2006
|
+
case "text": {
|
|
2007
|
+
const content = elementResource.document && "content" in elementResource.document ? parseDocument(elementResource.document.content) : null;
|
|
2008
|
+
if (!content) throw new Error(`Content not found for the following component: “${componentName}”`);
|
|
2009
|
+
let variantName = "block";
|
|
2010
|
+
let variant;
|
|
2011
|
+
const variantProperty = getPropertyByLabel(componentProperty.properties, "variant");
|
|
2012
|
+
if (variantProperty !== null) {
|
|
2013
|
+
variantName = variantProperty.values[0].content;
|
|
2014
|
+
if (variantName === "paragraph" || variantName === "label" || variantName === "heading" || variantName === "display") {
|
|
2015
|
+
const size = getPropertyValueByLabel(variantProperty.properties, "size");
|
|
2016
|
+
variant = {
|
|
2017
|
+
name: variantName,
|
|
2018
|
+
size: size !== null ? size : "md"
|
|
2019
|
+
};
|
|
2020
|
+
} else variant = { name: variantName };
|
|
2021
|
+
} else variant = { name: variantName };
|
|
2022
|
+
properties.variant = variant;
|
|
2023
|
+
properties.content = content;
|
|
2024
|
+
break;
|
|
2025
|
+
}
|
|
2026
|
+
case "timeline": {
|
|
2027
|
+
const timelineLink = links.find((link) => link.category === "tree");
|
|
2028
|
+
if (!timelineLink) throw new Error(`Timeline link not found for the following component: “${componentName}”`);
|
|
2029
|
+
properties.timelineId = timelineLink.uuid;
|
|
2030
|
+
break;
|
|
2031
|
+
}
|
|
2032
|
+
case "video": {
|
|
2033
|
+
const videoLink = links.find((link) => link.type === "video");
|
|
2034
|
+
if (!videoLink) throw new Error(`Video link not found for the following component: “${componentName}”`);
|
|
2035
|
+
let isChaptersDislayed = getPropertyValueByLabel(componentProperty.properties, "chapters-displayed");
|
|
2036
|
+
isChaptersDislayed ??= true;
|
|
2037
|
+
properties.videoId = videoLink.uuid;
|
|
2038
|
+
properties.isChaptersDislayed = isChaptersDislayed === true;
|
|
2039
|
+
break;
|
|
2040
|
+
}
|
|
2041
|
+
default:
|
|
2042
|
+
console.warn(`Invalid or non-implemented component name “${String(unparsedComponentName)}” for the following element: “${parseStringContent(elementResource.identification.label)}”`);
|
|
2043
|
+
break;
|
|
2044
|
+
}
|
|
2045
|
+
return properties;
|
|
2046
|
+
}
|
|
2047
|
+
function parseWebTitle(properties, identification, overrides = {}) {
|
|
2048
|
+
const titleProperties = properties.find((property) => property.label === "presentation" && property.values[0].content === "title")?.properties;
|
|
2049
|
+
let variant = "default";
|
|
2050
|
+
let isNameDisplayed = overrides.isNameDisplayed ?? false;
|
|
2051
|
+
let isDescriptionDisplayed = false;
|
|
2052
|
+
let isDateDisplayed = false;
|
|
2053
|
+
let isCreatorsDisplayed = false;
|
|
2054
|
+
let isCountDisplayed = overrides.isCountDisplayed ?? false;
|
|
2055
|
+
if (titleProperties) {
|
|
2056
|
+
const titleVariant = getPropertyValueByLabel(titleProperties, "variant");
|
|
2057
|
+
if (titleVariant) variant = titleVariant;
|
|
2058
|
+
isNameDisplayed = getPropertyValueByLabel(titleProperties, "name-displayed") === true;
|
|
2059
|
+
isDescriptionDisplayed = getPropertyValueByLabel(titleProperties, "description-displayed") === true;
|
|
2060
|
+
isDateDisplayed = getPropertyValueByLabel(titleProperties, "date-displayed") === true;
|
|
2061
|
+
isCreatorsDisplayed = getPropertyValueByLabel(titleProperties, "creators-displayed") === true;
|
|
2062
|
+
isCountDisplayed = getPropertyValueByLabel(titleProperties, "count-displayed") === true;
|
|
2063
|
+
}
|
|
2064
|
+
return {
|
|
2065
|
+
label: identification.label,
|
|
2066
|
+
variant,
|
|
2067
|
+
properties: {
|
|
2068
|
+
isNameDisplayed,
|
|
2069
|
+
isDescriptionDisplayed,
|
|
2070
|
+
isDateDisplayed,
|
|
2071
|
+
isCreatorsDisplayed,
|
|
2072
|
+
isCountDisplayed
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
/**
|
|
2077
|
+
* Parses raw web element data into a standardized WebElement structure
|
|
2078
|
+
*
|
|
2079
|
+
* @param elementResource - Raw element resource data in OCHRE format
|
|
2080
|
+
* @returns Parsed WebElement object
|
|
2081
|
+
*/
|
|
2082
|
+
function parseWebElement(elementResource) {
|
|
2083
|
+
const identification = parseIdentification(elementResource.identification);
|
|
2084
|
+
const presentationProperty = (elementResource.properties?.property ? parseProperties(Array.isArray(elementResource.properties.property) ? elementResource.properties.property : [elementResource.properties.property]) : []).find((property) => property.label === "presentation");
|
|
2085
|
+
if (!presentationProperty) throw new Error(`Presentation property not found for element “${identification.label}”`);
|
|
2086
|
+
const componentProperty = presentationProperty.properties.find((property) => property.label === "component");
|
|
2087
|
+
if (!componentProperty) throw new Error(`Component for element “${identification.label}” not found`);
|
|
2088
|
+
const properties = parseWebElementProperties(componentProperty, elementResource);
|
|
2089
|
+
const elementResourceProperties = elementResource.properties?.property ? parseProperties(Array.isArray(elementResource.properties.property) ? elementResource.properties.property : [elementResource.properties.property]) : [];
|
|
2090
|
+
const cssProperties = elementResourceProperties.find((property) => property.label === "presentation" && property.values[0].content === "css")?.properties ?? [];
|
|
2091
|
+
const cssStyles = [];
|
|
2092
|
+
for (const property of cssProperties) {
|
|
2093
|
+
const cssStyle = property.values[0].content;
|
|
2094
|
+
cssStyles.push({
|
|
2095
|
+
label: property.label,
|
|
2096
|
+
value: cssStyle
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
const tabletCssProperties = elementResourceProperties.find((property) => property.label === "presentation" && property.values[0].content === "css-tablet")?.properties ?? [];
|
|
2100
|
+
const cssStylesTablet = [];
|
|
2101
|
+
for (const property of tabletCssProperties) {
|
|
2102
|
+
const cssStyle = property.values[0].content;
|
|
2103
|
+
cssStylesTablet.push({
|
|
2104
|
+
label: property.label,
|
|
2105
|
+
value: cssStyle
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
const mobileCssProperties = elementResourceProperties.find((property) => property.label === "presentation" && property.values[0].content === "css-mobile")?.properties ?? [];
|
|
2109
|
+
const cssStylesMobile = [];
|
|
2110
|
+
for (const property of mobileCssProperties) {
|
|
2111
|
+
const cssStyle = property.values[0].content;
|
|
2112
|
+
cssStylesMobile.push({
|
|
2113
|
+
label: property.label,
|
|
2114
|
+
value: cssStyle
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
const title = parseWebTitle(elementResourceProperties, identification, {
|
|
2118
|
+
isNameDisplayed: [
|
|
2119
|
+
"annotated-image",
|
|
2120
|
+
"annotated-document",
|
|
2121
|
+
"collection"
|
|
2122
|
+
].includes(properties.component),
|
|
2123
|
+
isCountDisplayed: properties.component === "collection" && properties.variant === "full"
|
|
2124
|
+
});
|
|
2125
|
+
return {
|
|
2126
|
+
uuid: elementResource.uuid,
|
|
2127
|
+
type: "element",
|
|
2128
|
+
title,
|
|
2129
|
+
cssStyles: {
|
|
2130
|
+
default: cssStyles,
|
|
2131
|
+
tablet: cssStylesTablet,
|
|
2132
|
+
mobile: cssStylesMobile
|
|
2133
|
+
},
|
|
2134
|
+
...properties
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Parses raw webpage data into a standardized Webpage structure
|
|
2139
|
+
*
|
|
2140
|
+
* @param webpageResource - Raw webpage resource data in OCHRE format
|
|
2141
|
+
* @returns Parsed Webpage object
|
|
2142
|
+
*/
|
|
2143
|
+
function parseWebpage(webpageResource) {
|
|
2144
|
+
const webpageProperties = webpageResource.properties ? parseProperties(Array.isArray(webpageResource.properties.property) ? webpageResource.properties.property : [webpageResource.properties.property]) : [];
|
|
2145
|
+
if (webpageProperties.length === 0 || webpageProperties.find((property) => property.label === "presentation")?.values[0]?.content !== "page") return null;
|
|
2146
|
+
const identification = parseIdentification(webpageResource.identification);
|
|
2147
|
+
const slug = webpageResource.slug;
|
|
2148
|
+
if (slug === void 0) throw new Error(`Slug not found for page “${identification.label}”`);
|
|
2149
|
+
const imageLink = (webpageResource.links ? parseLinks(Array.isArray(webpageResource.links) ? webpageResource.links : [webpageResource.links]) : []).find((link) => link.type === "image" || link.type === "IIIF");
|
|
2150
|
+
const webpageResources = webpageResource.resource ? Array.isArray(webpageResource.resource) ? webpageResource.resource : [webpageResource.resource] : [];
|
|
2151
|
+
const items = [];
|
|
2152
|
+
for (const resource of webpageResources) {
|
|
2153
|
+
const resourceType = getPropertyValueByLabel(resource.properties ? parseProperties(Array.isArray(resource.properties.property) ? resource.properties.property : [resource.properties.property]) : [], "presentation");
|
|
2154
|
+
if (resourceType == null) continue;
|
|
2155
|
+
switch (resourceType) {
|
|
2156
|
+
case "element": {
|
|
2157
|
+
const element = parseWebElement(resource);
|
|
2158
|
+
items.push(element);
|
|
2159
|
+
break;
|
|
2160
|
+
}
|
|
2161
|
+
case "block": {
|
|
2162
|
+
const block = parseWebBlock(resource);
|
|
2163
|
+
if (block) items.push(block);
|
|
2164
|
+
break;
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
const webpages = webpageResource.resource ? parseWebpageResources(Array.isArray(webpageResource.resource) ? webpageResource.resource : [webpageResource.resource], "page") : [];
|
|
2169
|
+
let displayedInHeader = true;
|
|
2170
|
+
let width = "default";
|
|
2171
|
+
let variant = "default";
|
|
2172
|
+
let isSidebarDisplayed = true;
|
|
2173
|
+
let isBreadcrumbsDisplayed = false;
|
|
2174
|
+
const webpageSubProperties = webpageProperties.find((property) => property.label === "presentation" && property.values[0]?.content === "page")?.properties;
|
|
2175
|
+
if (webpageSubProperties) {
|
|
2176
|
+
const headerProperty = webpageSubProperties.find((property) => property.label === "header")?.values[0];
|
|
2177
|
+
if (headerProperty) displayedInHeader = headerProperty.content === true;
|
|
2178
|
+
const widthProperty = webpageSubProperties.find((property) => property.label === "width")?.values[0];
|
|
2179
|
+
if (widthProperty) width = widthProperty.content;
|
|
2180
|
+
const variantProperty = webpageSubProperties.find((property) => property.label === "variant")?.values[0];
|
|
2181
|
+
if (variantProperty) variant = variantProperty.content;
|
|
2182
|
+
const isSidebarDisplayedProperty = webpageSubProperties.find((property) => property.label === "sidebar-visible")?.values[0];
|
|
2183
|
+
if (isSidebarDisplayedProperty) isSidebarDisplayed = isSidebarDisplayedProperty.content === true;
|
|
2184
|
+
const isBreadcrumbsDisplayedProperty = webpageSubProperties.find((property) => property.label === "breadcrumbs-visible")?.values[0];
|
|
2185
|
+
if (isBreadcrumbsDisplayedProperty) isBreadcrumbsDisplayed = isBreadcrumbsDisplayedProperty.content === true;
|
|
2186
|
+
}
|
|
2187
|
+
const cssStyleSubProperties = webpageProperties.find((property) => property.label === "presentation" && property.values[0]?.content === "css")?.properties;
|
|
2188
|
+
const cssStyles = [];
|
|
2189
|
+
if (cssStyleSubProperties) for (const property of cssStyleSubProperties) cssStyles.push({
|
|
2190
|
+
label: property.label,
|
|
2191
|
+
value: property.values[0].content
|
|
2192
|
+
});
|
|
2193
|
+
const tabletCssStyleSubProperties = webpageProperties.find((property) => property.label === "presentation" && property.values[0]?.content === "css-tablet")?.properties;
|
|
2194
|
+
const cssStylesTablet = [];
|
|
2195
|
+
if (tabletCssStyleSubProperties) for (const property of tabletCssStyleSubProperties) cssStylesTablet.push({
|
|
2196
|
+
label: property.label,
|
|
2197
|
+
value: property.values[0].content
|
|
2198
|
+
});
|
|
2199
|
+
const mobileCssStyleSubProperties = webpageProperties.find((property) => property.label === "presentation" && property.values[0]?.content === "css-mobile")?.properties;
|
|
2200
|
+
const cssStylesMobile = [];
|
|
2201
|
+
if (mobileCssStyleSubProperties) for (const property of mobileCssStyleSubProperties) cssStylesMobile.push({
|
|
2202
|
+
label: property.label,
|
|
2203
|
+
value: property.values[0].content
|
|
2204
|
+
});
|
|
2205
|
+
return {
|
|
2206
|
+
title: identification.label,
|
|
2207
|
+
slug,
|
|
2208
|
+
items,
|
|
2209
|
+
properties: {
|
|
2210
|
+
displayedInHeader,
|
|
2211
|
+
width,
|
|
2212
|
+
variant,
|
|
2213
|
+
backgroundImageUrl: imageLink ? `https://ochre.lib.uchicago.edu/ochre?uuid=${imageLink.uuid}&load` : null,
|
|
2214
|
+
isSidebarDisplayed,
|
|
2215
|
+
isBreadcrumbsDisplayed,
|
|
2216
|
+
cssStyles: {
|
|
2217
|
+
default: cssStyles,
|
|
2218
|
+
tablet: cssStylesTablet,
|
|
2219
|
+
mobile: cssStylesMobile
|
|
2220
|
+
}
|
|
2221
|
+
},
|
|
2222
|
+
webpages
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
/**
|
|
2226
|
+
* Parses raw webpage resources into an array of Webpage objects
|
|
2227
|
+
*
|
|
2228
|
+
* @param webpageResources - Array of raw webpage resources in OCHRE format
|
|
2229
|
+
* @returns Array of parsed Webpage objects
|
|
2230
|
+
*/
|
|
2231
|
+
function parseWebpages(webpageResources) {
|
|
2232
|
+
const returnPages = [];
|
|
2233
|
+
const pagesToParse = Array.isArray(webpageResources) ? webpageResources : [webpageResources];
|
|
2234
|
+
for (const page of pagesToParse) {
|
|
2235
|
+
const webpage = parseWebpage(page);
|
|
2236
|
+
if (webpage) returnPages.push(webpage);
|
|
2237
|
+
}
|
|
2238
|
+
return returnPages;
|
|
2239
|
+
}
|
|
2240
|
+
/**
|
|
2241
|
+
* Parses raw sidebar data into a standardized Sidebar structure
|
|
2242
|
+
*
|
|
2243
|
+
* @param resources - Array of raw sidebar resources in OCHRE format
|
|
2244
|
+
* @returns Parsed Sidebar object
|
|
2245
|
+
*/
|
|
2246
|
+
function parseSidebar(resources) {
|
|
2247
|
+
let sidebar = null;
|
|
2248
|
+
const sidebarElements = [];
|
|
2249
|
+
const sidebarTitle = {
|
|
2250
|
+
label: "",
|
|
2251
|
+
variant: "default",
|
|
2252
|
+
properties: {
|
|
2253
|
+
isNameDisplayed: false,
|
|
2254
|
+
isDescriptionDisplayed: false,
|
|
2255
|
+
isDateDisplayed: false,
|
|
2256
|
+
isCreatorsDisplayed: false,
|
|
2257
|
+
isCountDisplayed: false
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
let sidebarLayout = "start";
|
|
2261
|
+
let sidebarMobileLayout = "default";
|
|
2262
|
+
const sidebarCssStyles = [];
|
|
2263
|
+
const sidebarCssStylesTablet = [];
|
|
2264
|
+
const sidebarCssStylesMobile = [];
|
|
2265
|
+
const sidebarResource = resources.find((resource) => {
|
|
2266
|
+
return (resource.properties ? parseProperties(Array.isArray(resource.properties.property) ? resource.properties.property : [resource.properties.property]) : []).some((property) => property.label === "presentation" && property.values[0]?.content === "element" && property.properties[0]?.label === "component" && property.properties[0].values[0]?.content === "sidebar");
|
|
2267
|
+
});
|
|
2268
|
+
if (sidebarResource) {
|
|
2269
|
+
sidebarTitle.label = typeof sidebarResource.identification.label === "string" || typeof sidebarResource.identification.label === "number" || typeof sidebarResource.identification.label === "boolean" ? parseFakeString(sidebarResource.identification.label) : parseStringContent(sidebarResource.identification.label);
|
|
2270
|
+
const sidebarBaseProperties = sidebarResource.properties ? parseProperties(Array.isArray(sidebarResource.properties.property) ? sidebarResource.properties.property : [sidebarResource.properties.property]) : [];
|
|
2271
|
+
const sidebarProperties = sidebarBaseProperties.find((property) => property.label === "presentation" && property.values[0]?.content === "element")?.properties.find((property) => property.label === "component" && property.values[0]?.content === "sidebar")?.properties ?? [];
|
|
2272
|
+
const sidebarLayoutProperty = sidebarProperties.find((property) => property.label === "layout");
|
|
2273
|
+
if (sidebarLayoutProperty) sidebarLayout = sidebarLayoutProperty.values[0].content;
|
|
2274
|
+
const sidebarMobileLayoutProperty = sidebarProperties.find((property) => property.label === "layout-mobile");
|
|
2275
|
+
if (sidebarMobileLayoutProperty) sidebarMobileLayout = sidebarMobileLayoutProperty.values[0].content;
|
|
2276
|
+
const cssProperties = sidebarBaseProperties.find((property) => property.label === "presentation" && property.values[0].content === "css")?.properties ?? [];
|
|
2277
|
+
for (const property of cssProperties) {
|
|
2278
|
+
const cssStyle = property.values[0].content;
|
|
2279
|
+
sidebarCssStyles.push({
|
|
2280
|
+
label: property.label,
|
|
2281
|
+
value: cssStyle
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
const tabletCssProperties = sidebarBaseProperties.find((property) => property.label === "presentation" && property.values[0].content === "css-tablet")?.properties ?? [];
|
|
2285
|
+
for (const property of tabletCssProperties) {
|
|
2286
|
+
const cssStyle = property.values[0].content;
|
|
2287
|
+
sidebarCssStylesTablet.push({
|
|
2288
|
+
label: property.label,
|
|
2289
|
+
value: cssStyle
|
|
2290
|
+
});
|
|
2291
|
+
}
|
|
2292
|
+
const mobileCssProperties = sidebarBaseProperties.find((property) => property.label === "presentation" && property.values[0].content === "css-mobile")?.properties ?? [];
|
|
2293
|
+
for (const property of mobileCssProperties) {
|
|
2294
|
+
const cssStyle = property.values[0].content;
|
|
2295
|
+
sidebarCssStylesMobile.push({
|
|
2296
|
+
label: property.label,
|
|
2297
|
+
value: cssStyle
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
const titleProperties = sidebarBaseProperties.find((property) => property.label === "presentation" && property.values[0].content === "title")?.properties;
|
|
2301
|
+
if (titleProperties) {
|
|
2302
|
+
const titleVariant = getPropertyValueByLabel(titleProperties, "variant");
|
|
2303
|
+
if (titleVariant) sidebarTitle.variant = titleVariant;
|
|
2304
|
+
sidebarTitle.properties.isNameDisplayed = getPropertyValueByLabel(titleProperties, "name-displayed") === true;
|
|
2305
|
+
sidebarTitle.properties.isDescriptionDisplayed = getPropertyValueByLabel(titleProperties, "description-displayed") === true;
|
|
2306
|
+
sidebarTitle.properties.isDateDisplayed = getPropertyValueByLabel(titleProperties, "date-displayed") === true;
|
|
2307
|
+
sidebarTitle.properties.isCreatorsDisplayed = getPropertyValueByLabel(titleProperties, "creators-displayed") === true;
|
|
2308
|
+
sidebarTitle.properties.isCountDisplayed = getPropertyValueByLabel(titleProperties, "count-displayed") === true;
|
|
2309
|
+
}
|
|
2310
|
+
const sidebarResources = sidebarResource.resource ? Array.isArray(sidebarResource.resource) ? sidebarResource.resource : [sidebarResource.resource] : [];
|
|
2311
|
+
for (const resource of sidebarResources) {
|
|
2312
|
+
const element = parseWebElement(resource);
|
|
2313
|
+
sidebarElements.push(element);
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
if (sidebarElements.length > 0) sidebar = {
|
|
2317
|
+
elements: sidebarElements,
|
|
2318
|
+
title: sidebarTitle,
|
|
2319
|
+
layout: sidebarLayout,
|
|
2320
|
+
mobileLayout: sidebarMobileLayout,
|
|
2321
|
+
cssStyles: {
|
|
2322
|
+
default: sidebarCssStyles,
|
|
2323
|
+
tablet: sidebarCssStylesTablet,
|
|
2324
|
+
mobile: sidebarCssStylesMobile
|
|
2325
|
+
}
|
|
2326
|
+
};
|
|
2327
|
+
return sidebar;
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Parses raw text element data for accordion layout with items support
|
|
2331
|
+
*
|
|
2332
|
+
* @param elementResource - Raw element resource data in OCHRE format
|
|
2333
|
+
* @returns Parsed text WebElement with items array
|
|
2334
|
+
*/
|
|
2335
|
+
function parseWebElementForAccordion(elementResource) {
|
|
2336
|
+
const textElement = parseWebElement(elementResource);
|
|
2337
|
+
const childResources = elementResource.resource ? Array.isArray(elementResource.resource) ? elementResource.resource : [elementResource.resource] : [];
|
|
2338
|
+
const items = [];
|
|
2339
|
+
for (const resource of childResources) {
|
|
2340
|
+
const resourceType = getPropertyValueByLabel(resource.properties ? parseProperties(Array.isArray(resource.properties.property) ? resource.properties.property : [resource.properties.property]) : [], "presentation");
|
|
2341
|
+
if (resourceType == null) continue;
|
|
2342
|
+
switch (resourceType) {
|
|
2343
|
+
case "element": {
|
|
2344
|
+
const element = parseWebElement(resource);
|
|
2345
|
+
items.push(element);
|
|
2346
|
+
break;
|
|
2347
|
+
}
|
|
2348
|
+
case "block": {
|
|
2349
|
+
const block = parseWebBlock(resource);
|
|
2350
|
+
if (block) items.push(block);
|
|
2351
|
+
break;
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
return {
|
|
2356
|
+
...textElement,
|
|
2357
|
+
items
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Parses raw block data into a standardized WebBlock structure
|
|
2362
|
+
*
|
|
2363
|
+
* @param blockResource - Raw block resource data in OCHRE format
|
|
2364
|
+
* @returns Parsed WebBlock object
|
|
2365
|
+
*/
|
|
2366
|
+
function parseWebBlock(blockResource) {
|
|
2367
|
+
const blockProperties = blockResource.properties ? parseProperties(Array.isArray(blockResource.properties.property) ? blockResource.properties.property : [blockResource.properties.property]) : [];
|
|
2368
|
+
const returnBlock = {
|
|
2369
|
+
uuid: blockResource.uuid,
|
|
2370
|
+
type: "block",
|
|
2371
|
+
title: parseWebTitle(blockProperties, parseIdentification(blockResource.identification)),
|
|
2372
|
+
items: [],
|
|
2373
|
+
properties: {
|
|
2374
|
+
default: {
|
|
2375
|
+
layout: "vertical",
|
|
2376
|
+
spacing: void 0,
|
|
2377
|
+
gap: void 0,
|
|
2378
|
+
alignItems: "start",
|
|
2379
|
+
justifyContent: "stretch"
|
|
2380
|
+
},
|
|
2381
|
+
mobile: null,
|
|
2382
|
+
tablet: null
|
|
2383
|
+
},
|
|
2384
|
+
cssStyles: {
|
|
2385
|
+
default: [],
|
|
2386
|
+
tablet: [],
|
|
2387
|
+
mobile: []
|
|
2388
|
+
}
|
|
2389
|
+
};
|
|
2390
|
+
const blockMainProperties = blockProperties.find((property) => property.label === "presentation" && property.values[0]?.content === "block")?.properties;
|
|
2391
|
+
if (blockMainProperties) {
|
|
2392
|
+
const layoutProperty = blockMainProperties.find((property) => property.label === "layout")?.values[0];
|
|
2393
|
+
if (layoutProperty) returnBlock.properties.default.layout = layoutProperty.content;
|
|
2394
|
+
if (returnBlock.properties.default.layout === "accordion") {
|
|
2395
|
+
const isAccordionEnabledProperty = blockMainProperties.find((property) => property.label === "accordion-enabled")?.values[0];
|
|
2396
|
+
if (isAccordionEnabledProperty) returnBlock.properties.default.isAccordionEnabled = isAccordionEnabledProperty.content === true;
|
|
2397
|
+
else returnBlock.properties.default.isAccordionEnabled = true;
|
|
2398
|
+
const isAccordionExpandedByDefaultProperty = blockMainProperties.find((property) => property.label === "accordion-expanded")?.values[0];
|
|
2399
|
+
if (isAccordionExpandedByDefaultProperty) returnBlock.properties.default.isAccordionExpandedByDefault = isAccordionExpandedByDefaultProperty.content === true;
|
|
2400
|
+
else returnBlock.properties.default.isAccordionExpandedByDefault = false;
|
|
2401
|
+
const isAccordionSidebarDisplayedProperty = blockMainProperties.find((property) => property.label === "accordion-sidebar-displayed")?.values[0];
|
|
2402
|
+
if (isAccordionSidebarDisplayedProperty) returnBlock.properties.default.isAccordionSidebarDisplayed = isAccordionSidebarDisplayedProperty.content === true;
|
|
2403
|
+
else returnBlock.properties.default.isAccordionSidebarDisplayed = false;
|
|
2404
|
+
}
|
|
2405
|
+
const spacingProperty = blockMainProperties.find((property) => property.label === "spacing")?.values[0];
|
|
2406
|
+
if (spacingProperty) returnBlock.properties.default.spacing = spacingProperty.content;
|
|
2407
|
+
const gapProperty = blockMainProperties.find((property) => property.label === "gap")?.values[0];
|
|
2408
|
+
if (gapProperty) returnBlock.properties.default.gap = gapProperty.content;
|
|
2409
|
+
const alignItemsProperty = blockMainProperties.find((property) => property.label === "align-items")?.values[0];
|
|
2410
|
+
if (alignItemsProperty) returnBlock.properties.default.alignItems = alignItemsProperty.content;
|
|
2411
|
+
const justifyContentProperty = blockMainProperties.find((property) => property.label === "justify-content")?.values[0];
|
|
2412
|
+
if (justifyContentProperty) returnBlock.properties.default.justifyContent = justifyContentProperty.content;
|
|
2413
|
+
const tabletOverwriteProperty = blockMainProperties.find((property) => property.label === "overwrite-tablet");
|
|
2414
|
+
if (tabletOverwriteProperty) {
|
|
2415
|
+
const tabletOverwriteProperties = tabletOverwriteProperty.properties;
|
|
2416
|
+
const propertiesTablet = {};
|
|
2417
|
+
const layoutProperty$1 = tabletOverwriteProperties.find((property) => property.label === "layout")?.values[0];
|
|
2418
|
+
if (layoutProperty$1) propertiesTablet.layout = layoutProperty$1.content;
|
|
2419
|
+
if (propertiesTablet.layout === "accordion" || returnBlock.properties.default.layout === "accordion") {
|
|
2420
|
+
const isAccordionEnabledProperty = tabletOverwriteProperties.find((property) => property.label === "accordion-enabled")?.values[0];
|
|
2421
|
+
if (isAccordionEnabledProperty) propertiesTablet.isAccordionEnabled = isAccordionEnabledProperty.content === true;
|
|
2422
|
+
const isAccordionExpandedByDefaultProperty = tabletOverwriteProperties.find((property) => property.label === "accordion-expanded")?.values[0];
|
|
2423
|
+
if (isAccordionExpandedByDefaultProperty) propertiesTablet.isAccordionExpandedByDefault = isAccordionExpandedByDefaultProperty.content === true;
|
|
2424
|
+
const isAccordionSidebarDisplayedProperty = tabletOverwriteProperties.find((property) => property.label === "accordion-sidebar-displayed")?.values[0];
|
|
2425
|
+
if (isAccordionSidebarDisplayedProperty) propertiesTablet.isAccordionSidebarDisplayed = isAccordionSidebarDisplayedProperty.content === true;
|
|
2426
|
+
}
|
|
2427
|
+
const spacingProperty$1 = tabletOverwriteProperties.find((property) => property.label === "spacing")?.values[0];
|
|
2428
|
+
if (spacingProperty$1) propertiesTablet.spacing = spacingProperty$1.content;
|
|
2429
|
+
const gapProperty$1 = tabletOverwriteProperties.find((property) => property.label === "gap")?.values[0];
|
|
2430
|
+
if (gapProperty$1) propertiesTablet.gap = gapProperty$1.content;
|
|
2431
|
+
const alignItemsProperty$1 = tabletOverwriteProperties.find((property) => property.label === "align-items")?.values[0];
|
|
2432
|
+
if (alignItemsProperty$1) propertiesTablet.alignItems = alignItemsProperty$1.content;
|
|
2433
|
+
const justifyContentProperty$1 = tabletOverwriteProperties.find((property) => property.label === "justify-content")?.values[0];
|
|
2434
|
+
if (justifyContentProperty$1) propertiesTablet.justifyContent = justifyContentProperty$1.content;
|
|
2435
|
+
returnBlock.properties.tablet = propertiesTablet;
|
|
2436
|
+
}
|
|
2437
|
+
const mobileOverwriteProperty = blockMainProperties.find((property) => property.label === "overwrite-mobile");
|
|
2438
|
+
if (mobileOverwriteProperty) {
|
|
2439
|
+
const mobileOverwriteProperties = mobileOverwriteProperty.properties;
|
|
2440
|
+
const propertiesMobile = {};
|
|
2441
|
+
const layoutProperty$1 = mobileOverwriteProperties.find((property) => property.label === "layout")?.values[0];
|
|
2442
|
+
if (layoutProperty$1) propertiesMobile.layout = layoutProperty$1.content;
|
|
2443
|
+
if (propertiesMobile.layout === "accordion" || returnBlock.properties.default.layout === "accordion") {
|
|
2444
|
+
const isAccordionEnabledProperty = mobileOverwriteProperties.find((property) => property.label === "accordion-enabled")?.values[0];
|
|
2445
|
+
if (isAccordionEnabledProperty) propertiesMobile.isAccordionEnabled = isAccordionEnabledProperty.content === true;
|
|
2446
|
+
const isAccordionExpandedByDefaultProperty = mobileOverwriteProperties.find((property) => property.label === "accordion-expanded")?.values[0];
|
|
2447
|
+
if (isAccordionExpandedByDefaultProperty) propertiesMobile.isAccordionExpandedByDefault = isAccordionExpandedByDefaultProperty.content === true;
|
|
2448
|
+
const isAccordionSidebarDisplayedProperty = mobileOverwriteProperties.find((property) => property.label === "accordion-sidebar-displayed")?.values[0];
|
|
2449
|
+
if (isAccordionSidebarDisplayedProperty) propertiesMobile.isAccordionSidebarDisplayed = isAccordionSidebarDisplayedProperty.content === true;
|
|
2450
|
+
}
|
|
2451
|
+
const spacingProperty$1 = mobileOverwriteProperties.find((property) => property.label === "spacing")?.values[0];
|
|
2452
|
+
if (spacingProperty$1) propertiesMobile.spacing = spacingProperty$1.content;
|
|
2453
|
+
const gapProperty$1 = mobileOverwriteProperties.find((property) => property.label === "gap")?.values[0];
|
|
2454
|
+
if (gapProperty$1) propertiesMobile.gap = gapProperty$1.content;
|
|
2455
|
+
const alignItemsProperty$1 = mobileOverwriteProperties.find((property) => property.label === "align-items")?.values[0];
|
|
2456
|
+
if (alignItemsProperty$1) propertiesMobile.alignItems = alignItemsProperty$1.content;
|
|
2457
|
+
const justifyContentProperty$1 = mobileOverwriteProperties.find((property) => property.label === "justify-content")?.values[0];
|
|
2458
|
+
if (justifyContentProperty$1) propertiesMobile.justifyContent = justifyContentProperty$1.content;
|
|
2459
|
+
returnBlock.properties.mobile = propertiesMobile;
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
const blockResources = blockResource.resource ? Array.isArray(blockResource.resource) ? blockResource.resource : [blockResource.resource] : [];
|
|
2463
|
+
if (returnBlock.properties.default.layout === "accordion") {
|
|
2464
|
+
const accordionItems = [];
|
|
2465
|
+
for (const resource of blockResources) {
|
|
2466
|
+
const resourceProperties = resource.properties ? parseProperties(Array.isArray(resource.properties.property) ? resource.properties.property : [resource.properties.property]) : [];
|
|
2467
|
+
const resourceType = getPropertyValueByLabel(resourceProperties, "presentation");
|
|
2468
|
+
if (resourceType !== "element") throw new Error(`Accordion only accepts elements, but got “${resourceType}” for the following resource: “${parseStringContent(resource.identification.label)}”`);
|
|
2469
|
+
const componentType = (resourceProperties.find((property) => property.label === "presentation")?.properties.find((property) => property.label === "component"))?.values[0]?.content;
|
|
2470
|
+
if (componentType !== "text") throw new Error(`Accordion only accepts text components, but got “${componentType}” for the following resource: “${parseStringContent(resource.identification.label)}”`);
|
|
2471
|
+
const element = parseWebElementForAccordion(resource);
|
|
2472
|
+
accordionItems.push(element);
|
|
2473
|
+
}
|
|
2474
|
+
returnBlock.items = accordionItems;
|
|
2475
|
+
} else {
|
|
2476
|
+
const blockItems = [];
|
|
2477
|
+
for (const resource of blockResources) {
|
|
2478
|
+
const resourceType = getPropertyValueByLabel(resource.properties ? parseProperties(Array.isArray(resource.properties.property) ? resource.properties.property : [resource.properties.property]) : [], "presentation");
|
|
2479
|
+
if (resourceType == null) continue;
|
|
2480
|
+
switch (resourceType) {
|
|
2481
|
+
case "element": {
|
|
2482
|
+
const element = parseWebElement(resource);
|
|
2483
|
+
blockItems.push(element);
|
|
2484
|
+
break;
|
|
2485
|
+
}
|
|
2486
|
+
case "block": {
|
|
2487
|
+
const block = parseWebBlock(resource);
|
|
2488
|
+
if (block) blockItems.push(block);
|
|
2489
|
+
break;
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
returnBlock.items = blockItems;
|
|
2494
|
+
}
|
|
2495
|
+
const blockCssStyles = blockProperties.find((property) => property.label === "presentation" && property.values[0]?.content === "css")?.properties;
|
|
2496
|
+
if (blockCssStyles) for (const property of blockCssStyles) returnBlock.cssStyles.default.push({
|
|
2497
|
+
label: property.label,
|
|
2498
|
+
value: property.values[0].content
|
|
2499
|
+
});
|
|
2500
|
+
const blockTabletCssStyles = blockProperties.find((property) => property.label === "presentation" && property.values[0]?.content === "css-tablet")?.properties;
|
|
2501
|
+
if (blockTabletCssStyles) for (const property of blockTabletCssStyles) returnBlock.cssStyles.tablet.push({
|
|
2502
|
+
label: property.label,
|
|
2503
|
+
value: property.values[0].content
|
|
2504
|
+
});
|
|
2505
|
+
const blockMobileCssStyles = blockProperties.find((property) => property.label === "presentation" && property.values[0]?.content === "css-mobile")?.properties;
|
|
2506
|
+
if (blockMobileCssStyles) for (const property of blockMobileCssStyles) returnBlock.cssStyles.mobile.push({
|
|
2507
|
+
label: property.label,
|
|
2508
|
+
value: property.values[0].content
|
|
2509
|
+
});
|
|
2510
|
+
return returnBlock;
|
|
2511
|
+
}
|
|
2512
|
+
/**
|
|
2513
|
+
* Parses raw website properties into a standardized WebsiteProperties structure
|
|
2514
|
+
*
|
|
2515
|
+
* @param properties - Array of raw website properties in OCHRE format
|
|
2516
|
+
* @returns Parsed WebsiteProperties object
|
|
2517
|
+
*/
|
|
2518
|
+
function parseWebsiteProperties(properties) {
|
|
2519
|
+
const websiteProperties = parseProperties(properties).find((property) => property.label === "presentation")?.properties;
|
|
2520
|
+
if (!websiteProperties) throw new Error("Presentation property not found");
|
|
2521
|
+
let type = websiteProperties.find((property) => property.label === "webUI")?.values[0]?.content;
|
|
2522
|
+
type ??= "traditional";
|
|
2523
|
+
let status = websiteProperties.find((property) => property.label === "status")?.values[0]?.content;
|
|
2524
|
+
status ??= "development";
|
|
2525
|
+
let privacy = websiteProperties.find((property) => property.label === "privacy")?.values[0]?.content;
|
|
2526
|
+
privacy ??= "public";
|
|
2527
|
+
const result = websiteSchema.safeParse({
|
|
2528
|
+
type,
|
|
2529
|
+
status,
|
|
2530
|
+
privacy
|
|
2531
|
+
});
|
|
2532
|
+
if (!result.success) throw new Error(`Invalid website properties: ${result.error.message}`);
|
|
2533
|
+
let contact = null;
|
|
2534
|
+
const contactProperty = websiteProperties.find((property) => property.label === "contact");
|
|
2535
|
+
if (contactProperty) {
|
|
2536
|
+
const [name, email] = (contactProperty.values[0]?.content).split(";");
|
|
2537
|
+
contact = {
|
|
2538
|
+
name,
|
|
2539
|
+
email: email ?? null
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
const logoUuid = websiteProperties.find((property) => property.label === "logo")?.values[0]?.uuid ?? null;
|
|
2543
|
+
let isHeaderDisplayed = true;
|
|
2544
|
+
let headerVariant = "default";
|
|
2545
|
+
let headerAlignment = "start";
|
|
2546
|
+
let isHeaderProjectDisplayed = true;
|
|
2547
|
+
let isFooterDisplayed = true;
|
|
2548
|
+
let isSidebarDisplayed = false;
|
|
2549
|
+
let iiifViewer = "universal-viewer";
|
|
2550
|
+
let supportsThemeToggle = true;
|
|
2551
|
+
let defaultTheme = null;
|
|
2552
|
+
const headerProperty = websiteProperties.find((property) => property.label === "navbar-visible")?.values[0];
|
|
2553
|
+
if (headerProperty) isHeaderDisplayed = headerProperty.content === true;
|
|
2554
|
+
const headerVariantProperty = websiteProperties.find((property) => property.label === "navbar-variant")?.values[0];
|
|
2555
|
+
if (headerVariantProperty) headerVariant = headerVariantProperty.content;
|
|
2556
|
+
const headerAlignmentProperty = websiteProperties.find((property) => property.label === "navbar-alignment")?.values[0];
|
|
2557
|
+
if (headerAlignmentProperty) headerAlignment = headerAlignmentProperty.content;
|
|
2558
|
+
const isHeaderProjectDisplayedProperty = websiteProperties.find((property) => property.label === "navbar-project-visible")?.values[0];
|
|
2559
|
+
if (isHeaderProjectDisplayedProperty) isHeaderProjectDisplayed = isHeaderProjectDisplayedProperty.content === true;
|
|
2560
|
+
const footerProperty = websiteProperties.find((property) => property.label === "footer-visible")?.values[0];
|
|
2561
|
+
if (footerProperty) isFooterDisplayed = footerProperty.content === true;
|
|
2562
|
+
const sidebarProperty = websiteProperties.find((property) => property.label === "sidebar-visible")?.values[0];
|
|
2563
|
+
if (sidebarProperty) isSidebarDisplayed = sidebarProperty.content === true;
|
|
2564
|
+
const iiifViewerProperty = websiteProperties.find((property) => property.label === "iiif-viewer")?.values[0];
|
|
2565
|
+
if (iiifViewerProperty) iiifViewer = iiifViewerProperty.content;
|
|
2566
|
+
const supportsThemeToggleProperty = websiteProperties.find((property) => property.label === "supports-theme-toggle")?.values[0];
|
|
2567
|
+
if (supportsThemeToggleProperty) supportsThemeToggle = supportsThemeToggleProperty.content === true;
|
|
2568
|
+
const defaultThemeProperty = websiteProperties.find((property) => property.label === "default-theme")?.values[0];
|
|
2569
|
+
if (defaultThemeProperty) defaultTheme = defaultThemeProperty.content;
|
|
2570
|
+
const { type: validatedType, status: validatedStatus, privacy: validatedPrivacy } = result.data;
|
|
2571
|
+
return {
|
|
2572
|
+
type: validatedType,
|
|
2573
|
+
privacy: validatedPrivacy,
|
|
2574
|
+
status: validatedStatus,
|
|
2575
|
+
contact,
|
|
2576
|
+
isHeaderDisplayed,
|
|
2577
|
+
headerVariant,
|
|
2578
|
+
headerAlignment,
|
|
2579
|
+
isHeaderProjectDisplayed,
|
|
2580
|
+
isFooterDisplayed,
|
|
2581
|
+
isSidebarDisplayed,
|
|
2582
|
+
iiifViewer,
|
|
2583
|
+
supportsThemeToggle,
|
|
2584
|
+
defaultTheme,
|
|
2585
|
+
logoUrl: logoUuid !== null ? `https://ochre.lib.uchicago.edu/ochre?uuid=${logoUuid}&load` : null
|
|
2586
|
+
};
|
|
2587
|
+
}
|
|
2588
|
+
function parseContexts(contexts) {
|
|
2589
|
+
const contextsParsed = [];
|
|
2590
|
+
for (const mainContext of contexts) {
|
|
2591
|
+
const contextItemsToParse = Array.isArray(mainContext.context) ? mainContext.context : [mainContext.context];
|
|
2592
|
+
for (const contextItemToParse of contextItemsToParse) {
|
|
2593
|
+
const levelsToParse = Array.isArray(contextItemToParse.levels.level) ? contextItemToParse.levels.level : [contextItemToParse.levels.level];
|
|
2594
|
+
let type = "";
|
|
2595
|
+
const levels = levelsToParse.map((level) => {
|
|
2596
|
+
let variableUuid = "";
|
|
2597
|
+
let valueUuid = null;
|
|
2598
|
+
if (typeof level === "string") {
|
|
2599
|
+
const splitLevel = level.split(", ");
|
|
2600
|
+
variableUuid = splitLevel[0];
|
|
2601
|
+
valueUuid = splitLevel[1] === "null" ? null : splitLevel[1];
|
|
2602
|
+
} else {
|
|
2603
|
+
const splitLevel = level.content.split(", ");
|
|
2604
|
+
type = level.dataType;
|
|
2605
|
+
variableUuid = splitLevel[0];
|
|
2606
|
+
valueUuid = splitLevel[1] === "null" ? null : splitLevel[1];
|
|
2607
|
+
}
|
|
2608
|
+
return {
|
|
2609
|
+
variableUuid,
|
|
2610
|
+
valueUuid
|
|
2611
|
+
};
|
|
2612
|
+
});
|
|
2613
|
+
contextsParsed.push({
|
|
2614
|
+
context: levels,
|
|
2615
|
+
type,
|
|
2616
|
+
identification: parseIdentification(contextItemToParse.identification)
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
return contextsParsed;
|
|
2621
|
+
}
|
|
2622
|
+
function parseWebsite(websiteTree, projectName, website) {
|
|
2623
|
+
if (!websiteTree.properties) throw new Error("Website properties not found");
|
|
2624
|
+
const properties = parseWebsiteProperties(Array.isArray(websiteTree.properties.property) ? websiteTree.properties.property : [websiteTree.properties.property]);
|
|
2625
|
+
if (typeof websiteTree.items === "string" || !("resource" in websiteTree.items)) throw new Error("Website pages not found");
|
|
2626
|
+
const resources = Array.isArray(websiteTree.items.resource) ? websiteTree.items.resource : [websiteTree.items.resource];
|
|
2627
|
+
const pages = parseWebpages(resources);
|
|
2628
|
+
const sidebar = parseSidebar(resources);
|
|
2629
|
+
let globalOptions = { contexts: {
|
|
2630
|
+
flatten: [],
|
|
2631
|
+
filter: [],
|
|
2632
|
+
sort: [],
|
|
2633
|
+
detail: [],
|
|
2634
|
+
download: [],
|
|
2635
|
+
label: [],
|
|
2636
|
+
suppress: [],
|
|
2637
|
+
prominent: []
|
|
2638
|
+
} };
|
|
2639
|
+
if (websiteTree.websiteOptions) {
|
|
2640
|
+
const flattenContextsRaw = websiteTree.websiteOptions.flattenContexts != null ? Array.isArray(websiteTree.websiteOptions.flattenContexts) ? websiteTree.websiteOptions.flattenContexts : [websiteTree.websiteOptions.flattenContexts] : [];
|
|
2641
|
+
const suppressContextsRaw = websiteTree.websiteOptions.suppressContexts != null ? Array.isArray(websiteTree.websiteOptions.suppressContexts) ? websiteTree.websiteOptions.suppressContexts : [websiteTree.websiteOptions.suppressContexts] : [];
|
|
2642
|
+
const filterContextsRaw = websiteTree.websiteOptions.filterContexts != null ? Array.isArray(websiteTree.websiteOptions.filterContexts) ? websiteTree.websiteOptions.filterContexts : [websiteTree.websiteOptions.filterContexts] : [];
|
|
2643
|
+
const sortContextsRaw = websiteTree.websiteOptions.sortContexts != null ? Array.isArray(websiteTree.websiteOptions.sortContexts) ? websiteTree.websiteOptions.sortContexts : [websiteTree.websiteOptions.sortContexts] : [];
|
|
2644
|
+
const detailContextsRaw = websiteTree.websiteOptions.detailContexts != null ? Array.isArray(websiteTree.websiteOptions.detailContexts) ? websiteTree.websiteOptions.detailContexts : [websiteTree.websiteOptions.detailContexts] : [];
|
|
2645
|
+
const downloadContextsRaw = websiteTree.websiteOptions.downloadContexts != null ? Array.isArray(websiteTree.websiteOptions.downloadContexts) ? websiteTree.websiteOptions.downloadContexts : [websiteTree.websiteOptions.downloadContexts] : [];
|
|
2646
|
+
const labelContextsRaw = websiteTree.websiteOptions.labelContexts != null ? Array.isArray(websiteTree.websiteOptions.labelContexts) ? websiteTree.websiteOptions.labelContexts : [websiteTree.websiteOptions.labelContexts] : [];
|
|
2647
|
+
const prominentContextsRaw = websiteTree.websiteOptions.prominentContexts != null ? Array.isArray(websiteTree.websiteOptions.prominentContexts) ? websiteTree.websiteOptions.prominentContexts : [websiteTree.websiteOptions.prominentContexts] : [];
|
|
2648
|
+
globalOptions = { contexts: {
|
|
2649
|
+
flatten: parseContexts(flattenContextsRaw),
|
|
2650
|
+
filter: parseContexts(filterContextsRaw),
|
|
2651
|
+
sort: parseContexts(sortContextsRaw),
|
|
2652
|
+
detail: parseContexts(detailContextsRaw),
|
|
2653
|
+
download: parseContexts(downloadContextsRaw),
|
|
2654
|
+
label: parseContexts(labelContextsRaw),
|
|
2655
|
+
suppress: parseContexts(suppressContextsRaw),
|
|
2656
|
+
prominent: parseContexts(prominentContextsRaw)
|
|
2657
|
+
} };
|
|
2658
|
+
}
|
|
2659
|
+
return {
|
|
2660
|
+
uuid: websiteTree.uuid,
|
|
2661
|
+
publicationDateTime: websiteTree.publicationDateTime ? new Date(websiteTree.publicationDateTime) : null,
|
|
2662
|
+
identification: parseIdentification(websiteTree.identification),
|
|
2663
|
+
project: {
|
|
2664
|
+
name: parseFakeString(projectName),
|
|
2665
|
+
website: website !== null ? parseFakeString(website) : null
|
|
2666
|
+
},
|
|
2667
|
+
creators: websiteTree.creators ? parsePersons(Array.isArray(websiteTree.creators.creator) ? websiteTree.creators.creator : [websiteTree.creators.creator]) : [],
|
|
2668
|
+
license: parseLicense(websiteTree.availability),
|
|
2669
|
+
sidebar,
|
|
2670
|
+
pages,
|
|
2671
|
+
properties,
|
|
2672
|
+
searchOptions: {
|
|
2673
|
+
filters: websiteTree.searchOptions?.filterUuids != null ? (Array.isArray(websiteTree.searchOptions.filterUuids.uuid) ? websiteTree.searchOptions.filterUuids.uuid : [websiteTree.searchOptions.filterUuids.uuid]).map((uuid) => ({
|
|
2674
|
+
uuid: uuid.content,
|
|
2675
|
+
type: uuid.type
|
|
2676
|
+
})) : [],
|
|
2677
|
+
attributeFilters: {
|
|
2678
|
+
bibliographies: websiteTree.searchOptions?.filterUuids?.filterBibliography ?? false,
|
|
2679
|
+
periods: websiteTree.searchOptions?.filterUuids?.filterPeriods ?? false
|
|
2680
|
+
},
|
|
2681
|
+
scopes: websiteTree.searchOptions?.scopes != null ? (Array.isArray(websiteTree.searchOptions.scopes.scope) ? websiteTree.searchOptions.scopes.scope : [websiteTree.searchOptions.scopes.scope]).map((scope) => ({
|
|
2682
|
+
uuid: scope.uuid.content,
|
|
2683
|
+
type: scope.uuid.type,
|
|
2684
|
+
identification: parseIdentification(scope.identification)
|
|
2685
|
+
})) : []
|
|
2686
|
+
},
|
|
2687
|
+
globalOptions
|
|
2688
|
+
};
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
//#endregion
|
|
2692
|
+
//#region src/utils/fetchers/gallery.ts
|
|
2693
|
+
/**
|
|
2694
|
+
* Fetches and parses a gallery from the OCHRE API
|
|
2695
|
+
*
|
|
2696
|
+
* @param uuid - The UUID of the gallery
|
|
2697
|
+
* @param filter - The filter to apply to the gallery
|
|
2698
|
+
* @param page - The page number to fetch
|
|
2699
|
+
* @param perPage - The number of items per page
|
|
2700
|
+
* @returns The parsed gallery or null if the fetch/parse fails
|
|
2701
|
+
*
|
|
2702
|
+
* @example
|
|
2703
|
+
* ```ts
|
|
2704
|
+
* const gallery = await fetchGallery("9c4da06b-f15e-40af-a747-0933eaf3587e", "1978", 1, 12);
|
|
2705
|
+
* if (gallery === null) {
|
|
2706
|
+
* console.error("Failed to fetch gallery");
|
|
2707
|
+
* return;
|
|
2708
|
+
* }
|
|
2709
|
+
* console.log(`Fetched gallery: ${gallery.identification.label}`);
|
|
2710
|
+
* console.log(`Contains ${gallery.resources.length.toLocaleString()} resources`);
|
|
2711
|
+
* ```
|
|
2712
|
+
*
|
|
2713
|
+
* @remarks
|
|
2714
|
+
* The returned gallery includes:
|
|
2715
|
+
* - Gallery metadata and identification
|
|
2716
|
+
* - Project identification
|
|
2717
|
+
* - Resources (gallery items)
|
|
2718
|
+
*/
|
|
2719
|
+
async function fetchGallery(uuid, filter, page, perPage, customFetch) {
|
|
2720
|
+
try {
|
|
2721
|
+
const { uuid: parsedUuid, filter: parsedFilter, page: parsedPage, perPage: parsedPerPage } = gallerySchema.parse({
|
|
2722
|
+
uuid,
|
|
2723
|
+
filter,
|
|
2724
|
+
page,
|
|
2725
|
+
perPage
|
|
2726
|
+
});
|
|
2727
|
+
const response = await (customFetch ?? fetch)(`https://ochre.lib.uchicago.edu/ochre?xquery=${encodeURIComponent(`
|
|
2728
|
+
for $q in input()/ochre[@uuid='${parsedUuid}']
|
|
2729
|
+
let $filtered := $q//items/resource[contains(lower-case(identification/label), lower-case('${parsedFilter}'))]
|
|
2730
|
+
let $maxLength := count($filtered)
|
|
2731
|
+
return <gallery maxLength='{$maxLength}'>
|
|
2732
|
+
{$q/metadata/project}
|
|
2733
|
+
{$q/metadata/item}
|
|
2734
|
+
{$filtered[position() >= ${((parsedPage - 1) * parsedPerPage + 1).toString()} and position() < ${(parsedPage * parsedPerPage + 1).toString()}]}
|
|
2735
|
+
</gallery>
|
|
2736
|
+
`)}&format=json`);
|
|
2737
|
+
if (!response.ok) throw new Error("Error fetching gallery items, please try again later.");
|
|
2738
|
+
const data = await response.json();
|
|
2739
|
+
if (!("gallery" in data.result)) throw new Error("Failed to fetch gallery");
|
|
2740
|
+
return {
|
|
2741
|
+
item: {
|
|
2742
|
+
identification: parseIdentification(data.result.gallery.item.identification),
|
|
2743
|
+
projectIdentification: parseIdentification(data.result.gallery.project.identification),
|
|
2744
|
+
resources: parseResources(data.result.gallery.resource ? Array.isArray(data.result.gallery.resource) ? data.result.gallery.resource : [data.result.gallery.resource] : []),
|
|
2745
|
+
maxLength: data.result.gallery.maxLength
|
|
2746
|
+
},
|
|
2747
|
+
error: null
|
|
2748
|
+
};
|
|
2749
|
+
} catch (error) {
|
|
2750
|
+
console.error(error);
|
|
2751
|
+
return {
|
|
2752
|
+
item: null,
|
|
2753
|
+
error: error instanceof Error ? error.message : "Failed to fetch gallery"
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
//#endregion
|
|
2759
|
+
//#region src/utils/fetchers/uuid.ts
|
|
2760
|
+
/**
|
|
2761
|
+
* Fetches raw OCHRE data by UUID from the OCHRE API
|
|
2762
|
+
*
|
|
2763
|
+
* @param uuid - The UUID of the OCHRE item to fetch
|
|
2764
|
+
* @returns A tuple containing either [null, OchreData] on success or [error message, null] on failure
|
|
2765
|
+
*
|
|
2766
|
+
* @example
|
|
2767
|
+
* ```ts
|
|
2768
|
+
* const [error, data] = await fetchByUuid("123e4567-e89b-12d3-a456-426614174000");
|
|
2769
|
+
* if (error !== null) {
|
|
2770
|
+
* console.error(`Failed to fetch: ${error}`);
|
|
2771
|
+
* return;
|
|
2772
|
+
* }
|
|
2773
|
+
* // Process data...
|
|
2774
|
+
* ```
|
|
2775
|
+
*
|
|
2776
|
+
* @internal
|
|
2777
|
+
*/
|
|
2778
|
+
async function fetchByUuid(uuid, customFetch) {
|
|
2779
|
+
try {
|
|
2780
|
+
const parsedUuid = uuidSchema.parse(uuid);
|
|
2781
|
+
const response = await (customFetch ?? fetch)(`https://ochre.lib.uchicago.edu/ochre?uuid=${parsedUuid}&format=json&lang="*"`);
|
|
2782
|
+
if (!response.ok) throw new Error("Failed to fetch OCHRE data");
|
|
2783
|
+
const dataRaw = await response.json();
|
|
2784
|
+
if (!("ochre" in dataRaw)) throw new Error("Invalid OCHRE data: API response missing 'ochre' key");
|
|
2785
|
+
return [null, dataRaw];
|
|
2786
|
+
} catch (error) {
|
|
2787
|
+
return [error instanceof Error ? error.message : "Unknown error", null];
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
//#endregion
|
|
2792
|
+
//#region src/utils/fetchers/item.ts
|
|
2793
|
+
/**
|
|
2794
|
+
* Fetches and parses an OCHRE item from the OCHRE API
|
|
2795
|
+
*
|
|
2796
|
+
* @param uuid - The UUID of the OCHRE item to fetch
|
|
2797
|
+
* @returns Object containing the parsed OCHRE item and its metadata, or null if the fetch/parse fails
|
|
2798
|
+
*
|
|
2799
|
+
* @example
|
|
2800
|
+
* ```ts
|
|
2801
|
+
* const result = await fetchItem("123e4567-e89b-12d3-a456-426614174000");
|
|
2802
|
+
* if (result === null) {
|
|
2803
|
+
* console.error("Failed to fetch OCHRE item");
|
|
2804
|
+
* return;
|
|
2805
|
+
* }
|
|
2806
|
+
* const { metadata, belongsTo, item, category } = result;
|
|
2807
|
+
* console.log(`Fetched OCHRE item: ${item.identification.label} with category ${category}`);
|
|
2808
|
+
* ```
|
|
2809
|
+
*
|
|
2810
|
+
* Or, if you want to fetch a specific category, you can do so by passing the category as an argument:
|
|
2811
|
+
* ```ts
|
|
2812
|
+
* const result = await fetchItem("123e4567-e89b-12d3-a456-426614174000", "resource");
|
|
2813
|
+
* const { metadata, belongsTo, item, category } = result;
|
|
2814
|
+
* console.log(item.category); // "resource"
|
|
2815
|
+
* ```
|
|
2816
|
+
*
|
|
2817
|
+
* @remarks
|
|
2818
|
+
* The returned OCHRE item includes:
|
|
2819
|
+
* - Item metadata
|
|
2820
|
+
* - Item belongsTo information
|
|
2821
|
+
* - Item content
|
|
2822
|
+
* - Item category
|
|
2823
|
+
*
|
|
2824
|
+
* If the fetch/parse fails, the returned object will have an `error` property.
|
|
2825
|
+
*/
|
|
2826
|
+
async function fetchItem(uuid, category, setCategory, customFetch) {
|
|
2827
|
+
try {
|
|
2828
|
+
const [error, data] = await fetchByUuid(uuid, customFetch);
|
|
2829
|
+
if (error !== null) throw new Error(error);
|
|
2830
|
+
const categoryKey = getItemCategory(Object.keys(data.ochre));
|
|
2831
|
+
let item;
|
|
2832
|
+
switch (categoryKey) {
|
|
2833
|
+
case "resource":
|
|
2834
|
+
if (!("resource" in data.ochre)) throw new Error("Invalid OCHRE data: API response missing 'resource' key");
|
|
2835
|
+
item = parseResource(data.ochre.resource);
|
|
2836
|
+
break;
|
|
2837
|
+
case "spatialUnit":
|
|
2838
|
+
if (!("spatialUnit" in data.ochre)) throw new Error("Invalid OCHRE data: API response missing 'spatialUnit' key");
|
|
2839
|
+
item = parseSpatialUnit(data.ochre.spatialUnit);
|
|
2840
|
+
break;
|
|
2841
|
+
case "concept":
|
|
2842
|
+
if (!("concept" in data.ochre)) throw new Error("Invalid OCHRE data: API response missing 'concept' key");
|
|
2843
|
+
item = parseConcept(data.ochre.concept);
|
|
2844
|
+
break;
|
|
2845
|
+
case "period":
|
|
2846
|
+
if (!("period" in data.ochre)) throw new Error("Invalid OCHRE data: API response missing 'period' key");
|
|
2847
|
+
item = parsePeriod(data.ochre.period);
|
|
2848
|
+
break;
|
|
2849
|
+
case "bibliography":
|
|
2850
|
+
if (!("bibliography" in data.ochre)) throw new Error("Invalid OCHRE data: API response missing 'bibliography' key");
|
|
2851
|
+
item = parseBibliography(data.ochre.bibliography);
|
|
2852
|
+
break;
|
|
2853
|
+
case "person":
|
|
2854
|
+
if (!("person" in data.ochre)) throw new Error("Invalid OCHRE data: API response missing 'person' key");
|
|
2855
|
+
item = parsePerson(data.ochre.person);
|
|
2856
|
+
break;
|
|
2857
|
+
case "propertyValue":
|
|
2858
|
+
if (!("propertyValue" in data.ochre)) throw new Error("Invalid OCHRE data: API response missing 'propertyValue' key");
|
|
2859
|
+
item = parsePropertyValue(data.ochre.propertyValue);
|
|
2860
|
+
break;
|
|
2861
|
+
case "set":
|
|
2862
|
+
if (!("set" in data.ochre)) throw new Error("Invalid OCHRE data: API response missing 'set' key");
|
|
2863
|
+
item = parseSet(data.ochre.set, setCategory);
|
|
2864
|
+
break;
|
|
2865
|
+
case "tree":
|
|
2866
|
+
if (!("tree" in data.ochre)) throw new Error("Invalid OCHRE data: API response missing 'tree' key");
|
|
2867
|
+
item = parseTree(data.ochre.tree, category, setCategory);
|
|
2868
|
+
break;
|
|
2869
|
+
default: throw new Error("Invalid category");
|
|
2870
|
+
}
|
|
2871
|
+
return {
|
|
2872
|
+
error: null,
|
|
2873
|
+
metadata: parseMetadata(data.ochre.metadata),
|
|
2874
|
+
belongsTo: {
|
|
2875
|
+
uuid: data.ochre.uuidBelongsTo,
|
|
2876
|
+
abbreviation: parseFakeString(data.ochre.belongsTo)
|
|
2877
|
+
},
|
|
2878
|
+
item,
|
|
2879
|
+
category
|
|
2880
|
+
};
|
|
2881
|
+
} catch (error) {
|
|
2882
|
+
return {
|
|
2883
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
2884
|
+
metadata: void 0,
|
|
2885
|
+
belongsTo: void 0,
|
|
2886
|
+
item: void 0,
|
|
2887
|
+
category: void 0
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
//#endregion
|
|
2893
|
+
//#region src/utils/fetchers/property-query.ts
|
|
2894
|
+
const PROJECT_SCOPE = "0c0aae37-7246-495b-9547-e25dbf5b99a3";
|
|
2895
|
+
const BELONG_TO_COLLECTION_UUID = "30054cb2-909a-4f34-8db9-8fe7369d691d";
|
|
2896
|
+
const UNASSIGNED_UUID = "e28e29af-b663-c0ac-ceb6-11a688fca0dd";
|
|
2897
|
+
/**
|
|
2898
|
+
* Check if a string is a valid UUID
|
|
2899
|
+
* @param value - The string to check
|
|
2900
|
+
* @returns True if the string is a valid UUID, false otherwise
|
|
2901
|
+
*/
|
|
2902
|
+
function isUUID(value) {
|
|
2903
|
+
return /^[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/i.test(value);
|
|
2904
|
+
}
|
|
2905
|
+
/**
|
|
2906
|
+
* Schema for a single item in the OCHRE API response
|
|
2907
|
+
*/
|
|
2908
|
+
const responseItemSchema = z.object({
|
|
2909
|
+
property: z.string().refine(isUUID),
|
|
2910
|
+
category: z.object({
|
|
2911
|
+
uuid: z.string().refine(isUUID),
|
|
2912
|
+
content: z.string()
|
|
2913
|
+
}),
|
|
2914
|
+
value: z.object({
|
|
2915
|
+
uuid: z.string().refine(isUUID).optional(),
|
|
2916
|
+
category: z.string().optional(),
|
|
2917
|
+
type: z.string().optional(),
|
|
2918
|
+
dataType: z.string(),
|
|
2919
|
+
publicationDateTime: z.iso.datetime().optional(),
|
|
2920
|
+
content: z.string().optional(),
|
|
2921
|
+
rawValue: z.string().optional()
|
|
2922
|
+
})
|
|
2923
|
+
});
|
|
2924
|
+
/**
|
|
2925
|
+
* Schema for the OCHRE API response
|
|
2926
|
+
*/
|
|
2927
|
+
const responseSchema = z.object({ result: z.object({ item: z.union([responseItemSchema, z.array(responseItemSchema)]) }) });
|
|
2928
|
+
/**
|
|
2929
|
+
* Build an XQuery string to fetch properties from the OCHRE API
|
|
2930
|
+
* @param scopeUuids - An array of scope UUIDs to filter by
|
|
2931
|
+
* @param propertyUuids - An array of property UUIDs to fetch
|
|
2932
|
+
* @returns An XQuery string
|
|
2933
|
+
*/
|
|
2934
|
+
function buildXQuery(scopeUuids, propertyUuids) {
|
|
2935
|
+
let collectionScopeFilter = "";
|
|
2936
|
+
if (scopeUuids.length > 0) collectionScopeFilter = `[properties/property[label/@uuid="${BELONG_TO_COLLECTION_UUID}"][value[${scopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")}]]]`;
|
|
2937
|
+
const propertyFilters = propertyUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ");
|
|
2938
|
+
return `for $q in input()/ochre[@uuidBelongsTo="${PROJECT_SCOPE}"]/*${collectionScopeFilter}/properties//property[label[${propertyFilters}]]
|
|
2939
|
+
return <item>
|
|
2940
|
+
<property>{xs:string($q/label/@uuid)}</property>
|
|
2941
|
+
<value> {$q/*[2]/@*} {$q/*[2]/content[1]/string/text()} </value>
|
|
2942
|
+
<category> {$q/ancestor::node()[local-name(.)="properties"]/../@uuid} {local-name($q/ancestor::node()[local-name(.)="properties"]/../self::node())} </category>
|
|
2943
|
+
</item>`;
|
|
2944
|
+
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Fetches and parses a property query from the OCHRE API
|
|
2947
|
+
*
|
|
2948
|
+
* @param scopeUuids - The scope UUIDs to filter by
|
|
2949
|
+
* @param propertyUuids - The property UUIDs to query by
|
|
2950
|
+
* @param customFetch - A custom fetch function to use instead of the default fetch
|
|
2951
|
+
* @returns The parsed property query or null if the fetch/parse fails
|
|
2952
|
+
*
|
|
2953
|
+
* @example
|
|
2954
|
+
* ```ts
|
|
2955
|
+
* const propertyQuery = await fetchPropertyQuery(["0c0aae37-7246-495b-9547-e25dbf5b99a3"], ["9c4da06b-f15e-40af-a747-0933eaf3587e"]);
|
|
2956
|
+
* if (propertyQuery === null) {
|
|
2957
|
+
* console.error("Failed to fetch property query");
|
|
2958
|
+
* return;
|
|
2959
|
+
* }
|
|
2960
|
+
* console.log(`Fetched property query: ${propertyQuery.item}`);
|
|
2961
|
+
* ```
|
|
2962
|
+
*
|
|
2963
|
+
* @remarks
|
|
2964
|
+
* The returned property query includes:
|
|
2965
|
+
* - Property items
|
|
2966
|
+
*/
|
|
2967
|
+
async function fetchPropertyQuery(scopeUuids, propertyUuids, customFetch) {
|
|
2968
|
+
try {
|
|
2969
|
+
const xquery = buildXQuery(scopeUuids, propertyUuids);
|
|
2970
|
+
const response = await (customFetch ?? fetch)(`https://ochre.lib.uchicago.edu/ochre?xquery=${encodeURIComponent(xquery)}&format=json`);
|
|
2971
|
+
if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`);
|
|
2972
|
+
const data = await response.json();
|
|
2973
|
+
const parsedResultRaw = responseSchema.parse(data);
|
|
2974
|
+
const parsedItems = Array.isArray(parsedResultRaw.result.item) ? parsedResultRaw.result.item : [parsedResultRaw.result.item];
|
|
2975
|
+
const items = {};
|
|
2976
|
+
for (const item of parsedItems) {
|
|
2977
|
+
const categoryUuid = item.category.uuid;
|
|
2978
|
+
const valueUuid = item.value.uuid;
|
|
2979
|
+
const valueContent = item.value.rawValue ?? item.value.content ?? "";
|
|
2980
|
+
if (valueContent in items) items[valueContent].resultUuids.push(categoryUuid);
|
|
2981
|
+
else items[valueContent] = {
|
|
2982
|
+
value: {
|
|
2983
|
+
uuid: valueUuid ?? null,
|
|
2984
|
+
category: item.value.category ?? null,
|
|
2985
|
+
type: item.value.type ?? null,
|
|
2986
|
+
dataType: item.value.dataType,
|
|
2987
|
+
publicationDateTime: item.value.publicationDateTime ?? null,
|
|
2988
|
+
content: item.value.rawValue ?? item.value.content ?? "",
|
|
2989
|
+
label: item.value.rawValue != null && item.value.content != null ? item.value.content : null
|
|
2990
|
+
},
|
|
2991
|
+
resultUuids: [categoryUuid]
|
|
2992
|
+
};
|
|
2993
|
+
}
|
|
2994
|
+
return {
|
|
2995
|
+
items: Object.values(items).filter((result) => result.value.uuid !== UNASSIGNED_UUID).toSorted((a, b) => {
|
|
2996
|
+
const aValue = a.value.label ?? a.value.content;
|
|
2997
|
+
const bValue = b.value.label ?? b.value.content;
|
|
2998
|
+
return aValue.localeCompare(bValue, "en-US");
|
|
2999
|
+
}),
|
|
3000
|
+
error: null
|
|
3001
|
+
};
|
|
3002
|
+
} catch (error) {
|
|
3003
|
+
console.error(error);
|
|
3004
|
+
return {
|
|
3005
|
+
items: null,
|
|
3006
|
+
error: error instanceof Error ? error.message : "Failed to fetch property query"
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
//#endregion
|
|
3012
|
+
//#region src/utils/fetchers/uuid-metadata.ts
|
|
3013
|
+
/**
|
|
3014
|
+
* Fetches raw OCHRE metadata by UUID from the OCHRE API
|
|
3015
|
+
*
|
|
3016
|
+
* @param uuid - The UUID of the OCHRE item to fetch
|
|
3017
|
+
* @returns An object containing the OCHRE metadata or an error message
|
|
3018
|
+
*
|
|
3019
|
+
* @example
|
|
3020
|
+
* ```ts
|
|
3021
|
+
* const { item, error } = await fetchByUuidMetadata("123e4567-e89b-12d3-a456-426614174000");
|
|
3022
|
+
* if (error !== null) {
|
|
3023
|
+
* console.error(`Failed to fetch: ${error}`);
|
|
3024
|
+
* return;
|
|
3025
|
+
* }
|
|
3026
|
+
* // Process data...
|
|
3027
|
+
* ```
|
|
3028
|
+
*/
|
|
3029
|
+
async function fetchByUuidMetadata(uuid, customFetch) {
|
|
3030
|
+
try {
|
|
3031
|
+
const parsedUuid = uuidSchema.parse(uuid);
|
|
3032
|
+
const response = await (customFetch ?? fetch)(`https://ochre.lib.uchicago.edu/ochre?xquery=${encodeURIComponent(`for $q in input()/ochre[@uuid='${parsedUuid}']/metadata return ($q/item, $q/project)`)}&format=json`);
|
|
3033
|
+
if (!response.ok) throw new Error("Failed to fetch metadata");
|
|
3034
|
+
const data = await response.json();
|
|
3035
|
+
const projectIdentification = {
|
|
3036
|
+
...parseIdentification(data.result.project.identification),
|
|
3037
|
+
website: data.result.project.identification.website ?? null
|
|
3038
|
+
};
|
|
3039
|
+
return {
|
|
3040
|
+
item: {
|
|
3041
|
+
item: {
|
|
3042
|
+
uuid,
|
|
3043
|
+
name: parseIdentification(data.result.item.identification).label,
|
|
3044
|
+
type: data.result.item.type
|
|
3045
|
+
},
|
|
3046
|
+
project: {
|
|
3047
|
+
name: projectIdentification.label,
|
|
3048
|
+
website: projectIdentification.website ?? null
|
|
3049
|
+
}
|
|
3050
|
+
},
|
|
3051
|
+
error: null
|
|
3052
|
+
};
|
|
3053
|
+
} catch (error) {
|
|
3054
|
+
return {
|
|
3055
|
+
item: null,
|
|
3056
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
//#endregion
|
|
3062
|
+
//#region src/utils/fetchers/website.ts
|
|
3063
|
+
const KNOWN_ABBREVIATIONS = {
|
|
3064
|
+
"uchicago-node": "60a1e386-7e53-4e14-b8cf-fb4ed953d57e",
|
|
3065
|
+
"uchicago-node-staging": "62b60a47-fad5-49d7-a06a-2fa059f6e79a",
|
|
3066
|
+
"guerrilla-television": "fad1e1bd-989d-4159-b195-4c32adc5cdc7",
|
|
3067
|
+
"mapping-chicagoland": "8db5e83e-0c06-48b7-b4ac-a060d9bb5689",
|
|
3068
|
+
"hannah-papanek": "20b2c919-021f-4774-b2c3-2f1ae5b910e7",
|
|
3069
|
+
mepa: "85ddaa5a-535b-4809-8714-855d2d812a3e",
|
|
3070
|
+
ssmc: "8ff977dd-d440-40f5-ad93-8ad7e2d39e74",
|
|
3071
|
+
"sosc-core-at-smart": "db26c953-9b2a-4691-a909-5e8726b531d7"
|
|
3072
|
+
};
|
|
3073
|
+
/**
|
|
3074
|
+
* Fetches and parses a website configuration from the OCHRE API
|
|
3075
|
+
*
|
|
3076
|
+
* @param abbreviation - The abbreviation identifier for the website
|
|
3077
|
+
* @returns The parsed website configuration or null if the fetch/parse fails
|
|
3078
|
+
*
|
|
3079
|
+
* @example
|
|
3080
|
+
* ```ts
|
|
3081
|
+
* const website = await fetchWebsite("guerrilla-television");
|
|
3082
|
+
* if (website === null) {
|
|
3083
|
+
* console.error("Failed to fetch website");
|
|
3084
|
+
* return;
|
|
3085
|
+
* }
|
|
3086
|
+
* console.log(`Fetched website: ${website.identification.label}`);
|
|
3087
|
+
* console.log(`Contains ${website.pages.length.toLocaleString()} pages`);
|
|
3088
|
+
* ```
|
|
3089
|
+
*
|
|
3090
|
+
* @remarks
|
|
3091
|
+
* The returned website configuration includes:
|
|
3092
|
+
* - Website metadata and identification
|
|
3093
|
+
* - Page structure and content
|
|
3094
|
+
* - Layout and styling properties
|
|
3095
|
+
* - Navigation configuration
|
|
3096
|
+
* - Sidebar elements
|
|
3097
|
+
* - Project information
|
|
3098
|
+
* - Creator details
|
|
3099
|
+
*
|
|
3100
|
+
* The abbreviation is case-insensitive and should match the website's configured abbreviation in OCHRE.
|
|
3101
|
+
*/
|
|
3102
|
+
async function fetchWebsite(abbreviation, customFetch) {
|
|
3103
|
+
try {
|
|
3104
|
+
const uuid = KNOWN_ABBREVIATIONS[abbreviation.toLocaleLowerCase("en-US")];
|
|
3105
|
+
const response = await (customFetch ?? fetch)(uuid != null ? `https://ochre.lib.uchicago.edu/ochre?uuid=${uuid}&format=json` : `https://ochre.lib.uchicago.edu/ochre?xquery=for $q in input()/ochre[tree[@type='lesson'][identification/abbreviation='${abbreviation.toLocaleLowerCase("en-US")}']] return $q&format=json`);
|
|
3106
|
+
if (!response.ok) throw new Error("Failed to fetch website");
|
|
3107
|
+
const data = await response.json();
|
|
3108
|
+
const result = "result" in data && !Array.isArray(data.result) ? data.result : !("result" in data) ? data : null;
|
|
3109
|
+
if (result == null || !("tree" in result.ochre)) throw new Error("Failed to fetch website");
|
|
3110
|
+
const projectIdentification = result.ochre.metadata.project?.identification ? parseIdentification(result.ochre.metadata.project.identification) : null;
|
|
3111
|
+
return [null, parseWebsite(result.ochre.tree, projectIdentification?.label ?? "", result.ochre.metadata.project?.identification.website ?? null)];
|
|
3112
|
+
} catch (error) {
|
|
3113
|
+
console.error(error);
|
|
3114
|
+
return [error instanceof Error ? error.message : "Unknown error", null];
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3118
|
+
//#endregion
|
|
3119
|
+
export { fetchByUuidMetadata, fetchGallery, fetchItem, fetchPropertyQuery, fetchWebsite, filterProperties, getPropertyByLabel, getPropertyByUuid, getPropertyValueByLabel, getPropertyValueByUuid, getPropertyValuesByLabel, getPropertyValuesByUuid, getUniqueProperties, getUniquePropertyLabels };
|