ochre-sdk 1.0.12 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +3 -1
  2. package/dist/constants.d.mts +17 -0
  3. package/dist/constants.mjs +85 -0
  4. package/dist/fetchers/gallery.d.mts +38 -0
  5. package/dist/fetchers/gallery.mjs +91 -0
  6. package/dist/fetchers/item-links.d.mts +32 -0
  7. package/dist/fetchers/item-links.mjs +120 -0
  8. package/dist/fetchers/item.d.mts +74 -0
  9. package/dist/fetchers/item.mjs +146 -0
  10. package/dist/fetchers/set/items.d.mts +48 -0
  11. package/dist/fetchers/set/items.mjs +268 -0
  12. package/dist/fetchers/set/property-values.d.mts +46 -0
  13. package/dist/fetchers/set/property-values.mjs +514 -0
  14. package/dist/fetchers/website.d.mts +25 -0
  15. package/dist/fetchers/website.mjs +38 -0
  16. package/dist/getters.d.mts +193 -0
  17. package/dist/getters.mjs +341 -0
  18. package/dist/helpers.d.mts +18 -0
  19. package/dist/helpers.mjs +33 -0
  20. package/dist/index.d.mts +12 -1966
  21. package/dist/index.mjs +9 -7201
  22. package/dist/parsers/helpers.d.mts +27 -0
  23. package/dist/parsers/helpers.mjs +53 -0
  24. package/dist/parsers/index.d.mts +65 -0
  25. package/dist/parsers/index.mjs +1338 -0
  26. package/dist/parsers/mdx.d.mts +4 -0
  27. package/dist/parsers/mdx.mjs +9 -0
  28. package/dist/parsers/multilingual.d.mts +189 -0
  29. package/dist/parsers/multilingual.mjs +410 -0
  30. package/dist/parsers/string.d.mts +29 -0
  31. package/dist/parsers/string.mjs +477 -0
  32. package/dist/parsers/website/index.d.mts +20 -0
  33. package/dist/parsers/website/index.mjs +1245 -0
  34. package/dist/parsers/website/reader.d.mts +29 -0
  35. package/dist/parsers/website/reader.mjs +75 -0
  36. package/dist/query.d.mts +13 -0
  37. package/dist/query.mjs +827 -0
  38. package/dist/schemas.d.mts +84 -0
  39. package/dist/schemas.mjs +232 -0
  40. package/dist/types/index.d.mts +840 -0
  41. package/dist/types/index.mjs +1 -0
  42. package/dist/types/website.d.mts +501 -0
  43. package/dist/types/website.mjs +1 -0
  44. package/dist/utils.d.mts +34 -0
  45. package/dist/utils.mjs +172 -0
  46. package/dist/xml/metadata.d.mts +5 -0
  47. package/dist/xml/metadata.mjs +30 -0
  48. package/dist/xml/schemas.d.mts +13 -0
  49. package/dist/xml/schemas.mjs +849 -0
  50. package/dist/xml/types.d.mts +901 -0
  51. package/dist/xml/types.mjs +1 -0
  52. package/package.json +19 -17
@@ -0,0 +1,514 @@
1
+ import { BELONGS_TO_COLLECTION_UUID, DEFAULT_LANGUAGES, XML_PARSER_OPTIONS } from "../../constants.mjs";
2
+ import { createSchemaValidationError, getErrorOutput, stringLiteral } from "../../utils.mjs";
3
+ import { MultilingualString } from "../../parsers/multilingual.mjs";
4
+ import { setPropertyValuesParamsSchema } from "../../schemas.mjs";
5
+ import { parseXMLContent } from "../../parsers/string.mjs";
6
+ import { buildAndCtsQueryExpression, buildBelongsToCollectionQueryExpression, buildQueryPlan } from "../../query.mjs";
7
+ import * as v from "valibot";
8
+ import { XMLParser } from "fast-xml-parser";
9
+ //#region src/fetchers/set/property-values.ts
10
+ function getLabelContentLanguages(content) {
11
+ const languages = [];
12
+ for (const contentItem of content) if (contentItem.lang !== "zxx" && !languages.includes(contentItem.lang)) languages.push(contentItem.lang);
13
+ return languages.length > 0 ? languages : [...DEFAULT_LANGUAGES];
14
+ }
15
+ function parsePropertyValueLabelText(text) {
16
+ if (text == null || text === "") return null;
17
+ return text === "<unassigned>" ? null : text;
18
+ }
19
+ function parsePropertyValueLabel(content, text) {
20
+ if (content != null && content.length > 0) return parseXMLContent({ content }, { languages: getLabelContentLanguages(content) });
21
+ const parsedText = parsePropertyValueLabelText(text);
22
+ if (parsedText == null) return null;
23
+ return MultilingualString.fromObject({ [DEFAULT_LANGUAGES[0]]: parsedText });
24
+ }
25
+ function parsePropertyValueBooleanContent(rawValue) {
26
+ if (rawValue == null || rawValue === "") return null;
27
+ return rawValue.toLocaleLowerCase("en-US") === "true";
28
+ }
29
+ function normalizePropertyValueDataType(dataType) {
30
+ const normalizedDataType = dataType.startsWith("xs:") ? dataType.slice(3) : dataType;
31
+ switch (normalizedDataType) {
32
+ case "IDREF":
33
+ case "boolean":
34
+ case "date":
35
+ case "dateTime":
36
+ case "decimal":
37
+ case "integer":
38
+ case "string":
39
+ case "time": return normalizedDataType;
40
+ default: return "string";
41
+ }
42
+ }
43
+ function sortPropertyValues(values) {
44
+ return values.toSorted((a, b) => {
45
+ if (a.count !== b.count) return b.count - a.count;
46
+ const label = a.label?.getText() ?? null;
47
+ const otherLabel = b.label?.getText() ?? null;
48
+ if (label !== otherLabel) return label?.localeCompare(otherLabel ?? "") ?? 0;
49
+ return a.content?.toString().localeCompare(b.content?.toString() ?? "") ?? 0;
50
+ });
51
+ }
52
+ function getPropertyValueKey(value) {
53
+ return `${value.dataType}|${typeof value.content}:${value.content.toLocaleString("en-US")}`;
54
+ }
55
+ function sortAttributeValues(values) {
56
+ return values.toSorted((a, b) => {
57
+ if (a.count !== b.count) return b.count - a.count;
58
+ return a.content.localeCompare(b.content);
59
+ });
60
+ }
61
+ const countSchema = v.pipe(v.optional(v.union([v.number(), v.string()]), 1), v.transform((val) => {
62
+ if (val === "") return 1;
63
+ const count = Number(val);
64
+ return Number.isFinite(count) ? count : 1;
65
+ }));
66
+ const propertyValueLabelStringSchema = v.object({ payload: v.optional(v.string(), "") });
67
+ const propertyValueLabelContentSchema = v.object({
68
+ lang: v.string(),
69
+ string: v.array(propertyValueLabelStringSchema)
70
+ });
71
+ function getPropertyVariableUuidsFromQueries(queries) {
72
+ const propertyVariableUuids = /* @__PURE__ */ new Set();
73
+ if (queries == null) return [];
74
+ const pendingQueries = [queries];
75
+ for (const query of pendingQueries) {
76
+ if ("target" in query) {
77
+ if (query.target !== "property") continue;
78
+ if (query.propertyVariable != null) propertyVariableUuids.add(query.propertyVariable);
79
+ continue;
80
+ }
81
+ pendingQueries.push(..."and" in query ? query.and : query.or);
82
+ }
83
+ return [...propertyVariableUuids];
84
+ }
85
+ function getItemFilterQueriesFromPropertyValueQueries(queries) {
86
+ if (queries == null) return null;
87
+ if ("target" in queries) {
88
+ if (queries.target !== "property") return queries;
89
+ if (queries.dataType === "date" || queries.dataType === "dateTime") return queries;
90
+ return "value" in queries && queries.value != null ? queries : null;
91
+ }
92
+ const filteredChildren = [];
93
+ const childQueries = "and" in queries ? queries.and : queries.or;
94
+ for (const childQuery of childQueries) {
95
+ const filteredChildQuery = getItemFilterQueriesFromPropertyValueQueries(childQuery);
96
+ if (filteredChildQuery != null) filteredChildren.push(filteredChildQuery);
97
+ }
98
+ if (filteredChildren.length === 0) return null;
99
+ if (filteredChildren.length === 1) return filteredChildren[0] ?? null;
100
+ return "and" in queries ? { and: filteredChildren } : { or: filteredChildren };
101
+ }
102
+ /**
103
+ * Schema for a single property value query item in the OCHRE API response
104
+ */
105
+ const propertyValueQueryItemSchema = v.pipe(v.object({
106
+ uuid: v.optional(v.string(), ""),
107
+ scope: v.optional(v.picklist(["global", "variable"]), "global"),
108
+ variableUuid: v.optional(v.string()),
109
+ count: countSchema,
110
+ globalCount: v.nullish(countSchema),
111
+ dataType: v.optional(v.string(), "string"),
112
+ rawValue: v.optional(v.string()),
113
+ payload: v.optional(v.string()),
114
+ content: v.optional(v.array(propertyValueLabelContentSchema))
115
+ }), v.transform((val) => {
116
+ const dataType = normalizePropertyValueDataType(val.dataType);
117
+ const label = parsePropertyValueLabel(val.content, val.payload);
118
+ const returnValue = {
119
+ scope: val.scope,
120
+ variableUuid: val.variableUuid != null && val.variableUuid !== "" ? val.variableUuid : null,
121
+ count: val.count,
122
+ globalCount: val.globalCount ?? null,
123
+ dataType,
124
+ content: null,
125
+ label
126
+ };
127
+ switch (dataType) {
128
+ case "IDREF":
129
+ returnValue.content = val.uuid !== "" ? val.uuid : null;
130
+ break;
131
+ case "integer":
132
+ case "decimal":
133
+ case "time":
134
+ if (val.rawValue != null && val.rawValue !== "") {
135
+ const numericContent = Number(val.rawValue);
136
+ returnValue.content = Number.isNaN(numericContent) ? null : numericContent;
137
+ }
138
+ break;
139
+ case "boolean":
140
+ returnValue.content = parsePropertyValueBooleanContent(val.rawValue);
141
+ break;
142
+ default: {
143
+ const labelText = label?.getText() ?? null;
144
+ returnValue.content = val.rawValue != null && val.rawValue !== "" ? val.rawValue.toString() : labelText;
145
+ break;
146
+ }
147
+ }
148
+ return returnValue;
149
+ }));
150
+ const attributeValueQueryItemSchema = v.pipe(v.object({
151
+ attributeType: v.picklist(["bibliographies", "periods"]),
152
+ count: countSchema,
153
+ content: v.optional(v.string()),
154
+ payload: v.optional(v.string())
155
+ }), v.transform((val) => ({
156
+ attributeType: val.attributeType,
157
+ count: val.count,
158
+ content: val.content != null && val.content !== "" ? val.content : val.payload != null && val.payload !== "" ? val.payload : null
159
+ })));
160
+ /**
161
+ * Schema for the property values OCHRE API response
162
+ */
163
+ const responseSchema = v.object({ result: v.object({ ochre: v.object({
164
+ payload: v.optional(v.string()),
165
+ propertyValue: v.optional(v.union([v.array(propertyValueQueryItemSchema), propertyValueQueryItemSchema])),
166
+ attributeValue: v.optional(v.union([v.array(attributeValueQueryItemSchema), attributeValueQueryItemSchema]))
167
+ }) }) });
168
+ /**
169
+ * Build an XQuery string to fetch property values from the OCHRE API
170
+ * @param params - The parameters for the fetch
171
+ * @param params.setScopeUuids - An array of set scope UUIDs to filter by
172
+ * @param params.belongsToCollectionScopeUuids - An array of collection scope UUIDs to filter by
173
+ * @param params.queries - Recursive query tree used to filter matching items
174
+ * @param params.propertyVariableUuids - Property variable UUIDs to aggregate, if any
175
+ * @param params.attributes - Whether to return values for bibliographies and periods
176
+ * @param params.attributes.bibliographies - Whether to return values for bibliographies
177
+ * @param params.attributes.periods - Whether to return values for periods
178
+ * @param params.isLimitedToLeafPropertyValues - Whether to limit the property values to leaf property values
179
+ * @returns An XQuery string
180
+ */
181
+ function buildXQuery(params) {
182
+ const { setScopeUuids, belongsToCollectionScopeUuids, queries, propertyVariableUuids, attributes, isLimitedToLeafPropertyValues } = params;
183
+ const setScopeDeclaration = `declare variable $setScopeUuids := (${setScopeUuids.map((uuid) => stringLiteral(uuid)).join(", ")});`;
184
+ const baseItemsExpression = "doc()/ochre/set[@uuid = $setScopeUuids]/items/*";
185
+ const compiledQueryPlan = buildQueryPlan({ queries: getItemFilterQueriesFromPropertyValueQueries(queries) });
186
+ const itemsQueryExpressions = [];
187
+ const belongsToCollectionQueryExpression = buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, BELONGS_TO_COLLECTION_UUID);
188
+ if (compiledQueryPlan.queryExpression != null) itemsQueryExpressions.push(compiledQueryPlan.queryExpression);
189
+ if (belongsToCollectionQueryExpression != null) itemsQueryExpressions.push(belongsToCollectionQueryExpression);
190
+ const itemsQueryExpression = buildAndCtsQueryExpression(itemsQueryExpressions);
191
+ const valueFilter = isLimitedToLeafPropertyValues ? "[not(@i)]" : "";
192
+ const queryBlocks = [];
193
+ const returnedSequences = [];
194
+ const xqueryDeclarations = [
195
+ "xquery version \"1.0-ml\";",
196
+ "declare namespace map = \"http://marklogic.com/xdmp/map\";",
197
+ setScopeDeclaration,
198
+ `declare function local:increment-count($counts, $key) {
199
+ let $current := map:get($counts, $key)
200
+ return map:put(
201
+ $counts,
202
+ $key,
203
+ if (empty($current)) then 1 else xs:integer($current) + 1
204
+ )
205
+ };
206
+
207
+ declare function local:value-display-text($v) {
208
+ if ($v/content)
209
+ then string-join($v/content[@xml:lang="eng"]//text(), "")
210
+ else string($v)
211
+ };
212
+
213
+ declare function local:value-label-content($v) {
214
+ if ($v/content) then
215
+ for $content in $v/content
216
+ let $lang := string($content/@xml:lang)
217
+ return <content lang="{$lang}"><string>{string-join($content//text(), "")}</string></content>
218
+ else ()
219
+ };
220
+
221
+ declare function local:value-content($data-type, $raw-value, $value-uuid, $display) {
222
+ if ($data-type = "IDREF") then $value-uuid
223
+ else if ($data-type = ("integer", "decimal", "time")) then
224
+ if ($raw-value castable as xs:double)
225
+ then string(xs:double($raw-value))
226
+ else ""
227
+ else if ($data-type = "boolean") then
228
+ if ($raw-value = "") then ""
229
+ else if (lower-case($raw-value) = "true") then "true"
230
+ else "false"
231
+ else if ($raw-value != "") then $raw-value
232
+ else if ($display != "" and $display != "<unassigned>") then $display
233
+ else ""
234
+ };
235
+
236
+ declare function local:value-kind($data-type) {
237
+ if ($data-type = ("integer", "decimal", "time")) then "number"
238
+ else if ($data-type = "boolean") then "boolean"
239
+ else "string"
240
+ };
241
+
242
+ declare function local:property-output-raw-value($data-type, $raw-value, $content) {
243
+ if ($data-type = ("integer", "decimal", "time", "boolean")) then $content
244
+ else $raw-value
245
+ };
246
+
247
+ declare function local:put-property-detail(
248
+ $details,
249
+ $key,
250
+ $scope,
251
+ $variable-uuid,
252
+ $value-uuid,
253
+ $raw-value,
254
+ $data-type,
255
+ $display,
256
+ $label-content
257
+ ) {
258
+ let $existing := map:get($details, $key)
259
+ return
260
+ if (
261
+ empty($existing)
262
+ or (not($existing/content) and exists($label-content))
263
+ or (
264
+ not($existing/content)
265
+ and string-length(string($existing)) = 0
266
+ and string-length($display) gt 0
267
+ )
268
+ ) then
269
+ map:put(
270
+ $details,
271
+ $key,
272
+ <propertyValue scope="{$scope}" variableUuid="{$variable-uuid}" uuid="{$value-uuid}" rawValue="{$raw-value}" dataType="{$data-type}">{
273
+ if (exists($label-content)) then $label-content else $display
274
+ }</propertyValue>
275
+ )
276
+ else ()
277
+ };
278
+
279
+ declare function local:add-property-facet(
280
+ $counts,
281
+ $details,
282
+ $seen,
283
+ $key,
284
+ $scope,
285
+ $variable-uuid,
286
+ $value-uuid,
287
+ $raw-value,
288
+ $data-type,
289
+ $display,
290
+ $label-content
291
+ ) {
292
+ if (exists(map:get($seen, $key))) then ()
293
+ else (
294
+ map:put($seen, $key, true()),
295
+ local:increment-count($counts, $key),
296
+ local:put-property-detail($details, $key, $scope, $variable-uuid, $value-uuid, $raw-value, $data-type, $display, $label-content)
297
+ )
298
+ };
299
+
300
+ declare function local:add-attribute-facet($counts, $seen, $key) {
301
+ if (exists(map:get($seen, $key))) then ()
302
+ else (
303
+ map:put($seen, $key, true()),
304
+ local:increment-count($counts, $key)
305
+ )
306
+ };`
307
+ ];
308
+ if (compiledQueryPlan.prolog !== "") xqueryDeclarations.push(compiledQueryPlan.prolog);
309
+ if (propertyVariableUuids.length > 0) {
310
+ const propertyVariableValues = propertyVariableUuids.map((uuid) => stringLiteral(uuid));
311
+ xqueryDeclarations.push(`declare variable $facetLabelUuids := (${propertyVariableValues.join(", ")});`);
312
+ queryBlocks.push(`let $global-property-counts := map:map()
313
+ let $variable-property-counts := map:map()
314
+ let $variable-property-details := map:map()
315
+ let $variable-property-global-keys := map:map()
316
+ let $_property-aggregation := xdmp:eager(
317
+ for $item in $items
318
+ let $global-seen := map:map()
319
+ let $variable-seen := map:map()
320
+ return
321
+ for $p in $item/properties/property[label/@uuid = $facetLabelUuids]
322
+ let $variable-uuid := string($p/label/@uuid)
323
+ for $v in $p/value${valueFilter}
324
+ let $value-uuid := string($v/@uuid)
325
+ let $raw-value := string($v/@rawValue)
326
+ let $data-type := string($v/@dataType)
327
+ let $display := local:value-display-text($v)
328
+ let $label-content := local:value-label-content($v)
329
+ let $content := local:value-content($data-type, $raw-value, $value-uuid, $display)
330
+ let $value-kind := local:value-kind($data-type)
331
+ let $output-raw-value := local:property-output-raw-value($data-type, $raw-value, $content)
332
+ let $global-key := string-join(($data-type, $value-kind, $content), "||")
333
+ let $variable-key := string-join(($variable-uuid, $data-type, $value-kind, $content), "||")
334
+ where $content != ""
335
+ return (
336
+ local:add-attribute-facet($global-property-counts, $global-seen, $global-key),
337
+ local:add-property-facet($variable-property-counts, $variable-property-details, $variable-seen, $variable-key, "variable", $variable-uuid, $value-uuid, $output-raw-value, $data-type, $display, $label-content),
338
+ map:put($variable-property-global-keys, $variable-key, $global-key)
339
+ )
340
+ )
341
+
342
+ let $property-values :=
343
+ (
344
+ $_property-aggregation,
345
+ for $key in map:keys($variable-property-counts)
346
+ let $detail := map:get($variable-property-details, $key)
347
+ let $global-key := map:get($variable-property-global-keys, $key)
348
+ return <propertyValue scope="variable" variableUuid="{string($detail/@variableUuid)}" uuid="{string($detail/@uuid)}" rawValue="{string($detail/@rawValue)}" dataType="{string($detail/@dataType)}" count="{map:get($variable-property-counts, $key)}" globalCount="{map:get($global-property-counts, $global-key)}">{
349
+ $detail/node()
350
+ }</propertyValue>
351
+ )`);
352
+ returnedSequences.push("$property-values");
353
+ }
354
+ if (attributes.bibliographies) {
355
+ queryBlocks.push(`let $bibliography-counts := map:map()
356
+ let $_bibliography-aggregation := xdmp:eager(
357
+ for $item in $items
358
+ let $seen := map:map()
359
+ return
360
+ for $bibliography in $item/bibliographies/bibliography
361
+ let $label := string-join($bibliography/identification/label/content[@xml:lang="eng"]//text(), "")
362
+ where string-length($label) gt 0
363
+ return local:add-attribute-facet($bibliography-counts, $seen, $label)
364
+ )
365
+
366
+ let $bibliography-values :=
367
+ (
368
+ $_bibliography-aggregation,
369
+ for $label in map:keys($bibliography-counts)
370
+ return <attributeValue attributeType="bibliographies" count="{map:get($bibliography-counts, $label)}" content="{$label}" />
371
+ )`);
372
+ returnedSequences.push("$bibliography-values");
373
+ }
374
+ if (attributes.periods) {
375
+ queryBlocks.push(`let $period-counts := map:map()
376
+ let $_period-aggregation := xdmp:eager(
377
+ for $item in $items
378
+ let $seen := map:map()
379
+ return
380
+ for $period in $item/periods/period
381
+ let $label := string-join($period/identification/label/content[@xml:lang="eng"]//text(), "")
382
+ where string-length($label) gt 0
383
+ return local:add-attribute-facet($period-counts, $seen, $label)
384
+ )
385
+
386
+ let $period-values :=
387
+ (
388
+ $_period-aggregation,
389
+ for $label in map:keys($period-counts)
390
+ return <attributeValue attributeType="periods" count="{map:get($period-counts, $label)}" content="{$label}" />
391
+ )`);
392
+ returnedSequences.push("$period-values");
393
+ }
394
+ const itemsClause = itemsQueryExpression == null ? `let $items := ${baseItemsExpression}` : `let $query := ${itemsQueryExpression}
395
+ let $items := cts:search(${baseItemsExpression}, $query)`;
396
+ return `${xqueryDeclarations.join("\n\n")}
397
+
398
+ <ochre>{
399
+ ${itemsClause}
400
+ ${queryBlocks.join("\n\n")}
401
+
402
+ return (${returnedSequences.join(", ")})
403
+ }</ochre>`;
404
+ }
405
+ /**
406
+ * Fetches and parses Set property values from the OCHRE API
407
+ *
408
+ * @param params - The parameters for the fetch
409
+ * @param params.setScopeUuids - An array of set scope UUIDs to filter by
410
+ * @param params.queries - Recursive query tree used to filter matching items
411
+ * @param params.attributes - Whether to return values for bibliographies and periods
412
+ * @param params.attributes.bibliographies - Whether to return values for bibliographies
413
+ * @param params.attributes.periods - Whether to return values for periods
414
+ * @param params.isLimitedToLeafPropertyValues - Whether to limit the property values to leaf property values
415
+ * @param options - Options for the fetch
416
+ * @param options.fetch - The fetch function to use
417
+ * @returns Parsed Set property values and requested attribute values.
418
+ * Returns empty arrays/objects when no matches are found, and null outputs on fetch/parse errors.
419
+ */
420
+ async function fetchSetPropertyValues(params, options) {
421
+ try {
422
+ const { setScopeUuids, belongsToCollectionScopeUuids, queries, attributes, isLimitedToLeafPropertyValues } = v.parse(setPropertyValuesParamsSchema, params);
423
+ const propertyVariableUuids = getPropertyVariableUuidsFromQueries(queries);
424
+ if (propertyVariableUuids.length === 0 && !attributes.bibliographies && !attributes.periods) return {
425
+ propertyValues: [],
426
+ propertyValuesByPropertyVariableUuid: {},
427
+ attributeValues: {
428
+ bibliographies: null,
429
+ periods: null
430
+ },
431
+ error: null,
432
+ detailedError: null
433
+ };
434
+ const xquery = buildXQuery({
435
+ setScopeUuids,
436
+ belongsToCollectionScopeUuids,
437
+ queries,
438
+ propertyVariableUuids,
439
+ attributes,
440
+ isLimitedToLeafPropertyValues
441
+ });
442
+ const response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
443
+ method: "POST",
444
+ body: xquery,
445
+ headers: { "Content-Type": "application/xquery" }
446
+ });
447
+ if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`, { cause: response.statusText });
448
+ const dataRaw = await response.text();
449
+ const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
450
+ const { success, issues, output } = v.safeParse(responseSchema, data);
451
+ if (!success) throw createSchemaValidationError("Failed to parse OCHRE Set property values", issues);
452
+ const parsedPropertyValues = [];
453
+ const parsedAttributeValues = [];
454
+ if (output.result.ochre.propertyValue != null) parsedPropertyValues.push(...Array.isArray(output.result.ochre.propertyValue) ? output.result.ochre.propertyValue : [output.result.ochre.propertyValue]);
455
+ if (output.result.ochre.attributeValue != null) parsedAttributeValues.push(...Array.isArray(output.result.ochre.attributeValue) ? output.result.ochre.attributeValue : [output.result.ochre.attributeValue]);
456
+ const propertyValuesByPropertyVariableUuid = {};
457
+ const flattenedPropertyValuesByKey = /* @__PURE__ */ new Map();
458
+ for (const propertyValue of parsedPropertyValues) {
459
+ if (propertyValue.content == null) continue;
460
+ const propertyValueItem = {
461
+ count: propertyValue.count,
462
+ dataType: propertyValue.dataType,
463
+ content: propertyValue.content,
464
+ label: propertyValue.label
465
+ };
466
+ const globalPropertyValueItem = {
467
+ count: propertyValue.globalCount ?? propertyValue.count,
468
+ dataType: propertyValue.dataType,
469
+ content: propertyValue.content,
470
+ label: propertyValue.label
471
+ };
472
+ const globalPropertyValueKey = getPropertyValueKey({
473
+ dataType: globalPropertyValueItem.dataType,
474
+ content: propertyValue.content
475
+ });
476
+ const existingGlobalPropertyValue = flattenedPropertyValuesByKey.get(globalPropertyValueKey);
477
+ if (existingGlobalPropertyValue == null) flattenedPropertyValuesByKey.set(globalPropertyValueKey, globalPropertyValueItem);
478
+ else if (existingGlobalPropertyValue.label == null && globalPropertyValueItem.label != null) existingGlobalPropertyValue.label = globalPropertyValueItem.label;
479
+ if (propertyValue.scope === "global") continue;
480
+ if (propertyValue.variableUuid != null) (propertyValuesByPropertyVariableUuid[propertyValue.variableUuid] ??= []).push(propertyValueItem);
481
+ }
482
+ for (const [propertyVariableUuid, values] of Object.entries(propertyValuesByPropertyVariableUuid)) propertyValuesByPropertyVariableUuid[propertyVariableUuid] = sortPropertyValues(values);
483
+ const attributeValuesByType = {
484
+ bibliographies: [],
485
+ periods: []
486
+ };
487
+ for (const attributeValue of parsedAttributeValues) {
488
+ if (attributeValue.content == null || attributeValue.content === "") continue;
489
+ attributeValuesByType[attributeValue.attributeType].push({
490
+ count: attributeValue.count,
491
+ content: attributeValue.content
492
+ });
493
+ }
494
+ return {
495
+ propertyValues: sortPropertyValues([...flattenedPropertyValuesByKey.values()]),
496
+ propertyValuesByPropertyVariableUuid,
497
+ attributeValues: {
498
+ bibliographies: attributes.bibliographies ? sortAttributeValues(attributeValuesByType.bibliographies) : null,
499
+ periods: attributes.periods ? sortAttributeValues(attributeValuesByType.periods) : null
500
+ },
501
+ error: null,
502
+ detailedError: null
503
+ };
504
+ } catch (error) {
505
+ return {
506
+ propertyValues: null,
507
+ propertyValuesByPropertyVariableUuid: null,
508
+ attributeValues: null,
509
+ ...getErrorOutput(error, "Failed to fetch property values")
510
+ };
511
+ }
512
+ }
513
+ //#endregion
514
+ export { fetchSetPropertyValues };
@@ -0,0 +1,25 @@
1
+ import { Website } from "../types/website.mjs";
2
+ import { LanguageCodes } from "../types/index.mjs";
3
+
4
+ //#region src/fetchers/website.d.ts
5
+ type FetchFunction = (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
6
+ /**
7
+ * Fetches and parses a website configuration from the OCHRE API
8
+ *
9
+ * @param abbreviation - The abbreviation identifier for the website
10
+ * @returns The parsed website configuration or null if the fetch/parse fails
11
+ */
12
+ declare function fetchWebsite<const T extends LanguageCodes = LanguageCodes>(abbreviation: string, options?: {
13
+ fetch?: FetchFunction;
14
+ languages?: T;
15
+ }): Promise<{
16
+ website: Website<T>;
17
+ error: null;
18
+ detailedError: null;
19
+ } | {
20
+ website: null;
21
+ error: string;
22
+ detailedError: string;
23
+ }>;
24
+ //#endregion
25
+ export { fetchWebsite };
@@ -0,0 +1,38 @@
1
+ import { XML_PARSER_OPTIONS } from "../constants.mjs";
2
+ import { createSchemaValidationError, getErrorOutput } from "../utils.mjs";
3
+ import { restoreXMLMetadata } from "../xml/metadata.mjs";
4
+ import { XMLWebsiteData } from "../xml/schemas.mjs";
5
+ import { parseWebsite } from "../parsers/website/index.mjs";
6
+ import * as v from "valibot";
7
+ import { XMLParser } from "fast-xml-parser";
8
+ //#region src/fetchers/website.ts
9
+ /**
10
+ * Fetches and parses a website configuration from the OCHRE API
11
+ *
12
+ * @param abbreviation - The abbreviation identifier for the website
13
+ * @returns The parsed website configuration or null if the fetch/parse fails
14
+ */
15
+ async function fetchWebsite(abbreviation, options) {
16
+ try {
17
+ const cleanAbbreviation = abbreviation.trim().toLocaleLowerCase("en-US");
18
+ const response = await (options?.fetch ?? fetch)(`https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery=${encodeURIComponent(`collection('ochre/tree')/ochre[tree/identification/abbreviation/content/string='${cleanAbbreviation}']`)}&xsl=none&lang="*"`);
19
+ if (!response.ok) throw new Error("Failed to fetch website", { cause: response.statusText });
20
+ const dataRaw = await response.text();
21
+ const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
22
+ const { success, issues, output } = v.safeParse(XMLWebsiteData, data);
23
+ if (!success) throw createSchemaValidationError("Failed to parse website XML", issues);
24
+ restoreXMLMetadata(output, data);
25
+ return {
26
+ website: parseWebsite(output, { languages: options?.languages }),
27
+ error: null,
28
+ detailedError: null
29
+ };
30
+ } catch (error) {
31
+ return {
32
+ website: null,
33
+ ...getErrorOutput(error, "Unknown error")
34
+ };
35
+ }
36
+ }
37
+ //#endregion
38
+ export { fetchWebsite };