ochre-sdk 1.0.13 → 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.
- package/dist/constants.d.mts +17 -0
- package/dist/constants.mjs +85 -0
- package/dist/fetchers/gallery.d.mts +38 -0
- package/dist/fetchers/gallery.mjs +91 -0
- package/dist/fetchers/item-links.d.mts +32 -0
- package/dist/fetchers/item-links.mjs +120 -0
- package/dist/fetchers/item.d.mts +74 -0
- package/dist/fetchers/item.mjs +146 -0
- package/dist/fetchers/set/items.d.mts +48 -0
- package/dist/fetchers/set/items.mjs +268 -0
- package/dist/fetchers/set/property-values.d.mts +46 -0
- package/dist/fetchers/set/property-values.mjs +514 -0
- package/dist/fetchers/website.d.mts +25 -0
- package/dist/fetchers/website.mjs +38 -0
- package/dist/getters.d.mts +193 -0
- package/dist/getters.mjs +341 -0
- package/dist/helpers.d.mts +18 -0
- package/dist/helpers.mjs +33 -0
- package/dist/index.d.mts +12 -1971
- package/dist/index.mjs +9 -7236
- package/dist/parsers/helpers.d.mts +27 -0
- package/dist/parsers/helpers.mjs +53 -0
- package/dist/parsers/index.d.mts +65 -0
- package/dist/parsers/index.mjs +1338 -0
- package/dist/parsers/mdx.d.mts +4 -0
- package/dist/parsers/mdx.mjs +9 -0
- package/dist/parsers/multilingual.d.mts +189 -0
- package/dist/parsers/multilingual.mjs +410 -0
- package/dist/parsers/string.d.mts +29 -0
- package/dist/parsers/string.mjs +477 -0
- package/dist/parsers/website/index.d.mts +20 -0
- package/dist/parsers/website/index.mjs +1245 -0
- package/dist/parsers/website/reader.d.mts +29 -0
- package/dist/parsers/website/reader.mjs +75 -0
- package/dist/query.d.mts +13 -0
- package/dist/query.mjs +827 -0
- package/dist/schemas.d.mts +84 -0
- package/dist/schemas.mjs +232 -0
- package/dist/types/index.d.mts +840 -0
- package/dist/types/index.mjs +1 -0
- package/dist/types/website.d.mts +501 -0
- package/dist/types/website.mjs +1 -0
- package/dist/utils.d.mts +34 -0
- package/dist/utils.mjs +172 -0
- package/dist/xml/metadata.d.mts +5 -0
- package/dist/xml/metadata.mjs +30 -0
- package/dist/xml/schemas.d.mts +13 -0
- package/dist/xml/schemas.mjs +849 -0
- package/dist/xml/types.d.mts +901 -0
- package/dist/xml/types.mjs +1 -0
- package/package.json +19 -17
|
@@ -0,0 +1,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 };
|