ochre-sdk 1.0.78 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -3
- package/dist/_virtual/_rolldown/runtime.mjs +13 -0
- package/dist/categories.d.mts +249 -0
- package/dist/categories.mjs +259 -0
- package/dist/constants.d.mts +14 -0
- package/dist/constants.mjs +18 -1
- package/dist/errors.d.mts +23 -0
- package/dist/{utilities.mjs → errors.mjs} +33 -84
- package/dist/fetchers/gallery.mjs +15 -51
- package/dist/fetchers/item-children.mjs +20 -66
- package/dist/fetchers/item-links.mjs +26 -79
- package/dist/fetchers/item-ocr-data.d.mts +2 -2
- package/dist/fetchers/item-ocr-data.mjs +11 -15
- package/dist/fetchers/item.d.mts +0 -15
- package/dist/fetchers/item.mjs +20 -72
- package/dist/fetchers/request.d.mts +70 -0
- package/dist/fetchers/request.mjs +100 -0
- package/dist/fetchers/set/items.mjs +27 -73
- package/dist/fetchers/set/property-values.d.mts +2 -3
- package/dist/fetchers/set/property-values.mjs +96 -130
- package/dist/fetchers/website-metadata.mjs +35 -57
- package/dist/fetchers/website.d.mts +2 -3
- package/dist/fetchers/website.mjs +22 -31
- package/dist/getters.d.mts +78 -148
- package/dist/getters.mjs +127 -208
- package/dist/helpers.d.mts +0 -4
- package/dist/helpers.mjs +19 -6
- package/dist/index.d.mts +8 -6
- package/dist/index.mjs +6 -4
- package/dist/ocr.d.mts +37 -0
- package/dist/ocr.mjs +52 -0
- package/dist/parsers/helpers.d.mts +21 -1
- package/dist/parsers/helpers.mjs +26 -6
- package/dist/parsers/index.d.mts +0 -8
- package/dist/parsers/index.mjs +118 -259
- package/dist/parsers/languages.d.mts +72 -0
- package/dist/parsers/languages.mjs +132 -0
- package/dist/parsers/multilingual.d.mts +49 -74
- package/dist/parsers/multilingual.mjs +88 -189
- package/dist/parsers/property-token.d.mts +34 -0
- package/dist/parsers/property-token.mjs +29 -0
- package/dist/parsers/string.d.mts +19 -0
- package/dist/parsers/string.mjs +45 -25
- package/dist/parsers/website/bounds.d.mts +10 -0
- package/dist/parsers/website/bounds.mjs +28 -0
- package/dist/parsers/website/components.d.mts +91 -0
- package/dist/parsers/website/components.mjs +681 -0
- package/dist/parsers/website/index.d.mts +0 -7
- package/dist/parsers/website/index.mjs +92 -1153
- package/dist/parsers/website/links.d.mts +36 -0
- package/dist/parsers/website/links.mjs +58 -0
- package/dist/parsers/website/messages.d.mts +24 -0
- package/dist/parsers/website/messages.mjs +31 -0
- package/dist/parsers/website/options.d.mts +6 -0
- package/dist/parsers/website/options.mjs +114 -0
- package/dist/parsers/website/properties.d.mts +12 -0
- package/dist/parsers/website/properties.mjs +158 -0
- package/dist/parsers/website/reader.d.mts +54 -4
- package/dist/parsers/website/reader.mjs +65 -20
- package/dist/parsers/website/slug.d.mts +64 -0
- package/dist/parsers/website/slug.mjs +82 -0
- package/dist/parsers/website/styles.d.mts +28 -0
- package/dist/parsers/website/styles.mjs +103 -0
- package/dist/parsers/website/walk.d.mts +68 -0
- package/dist/parsers/website/walk.mjs +116 -0
- package/dist/query.d.mts +66 -18
- package/dist/query.mjs +202 -48
- package/dist/reflection.d.mts +64 -0
- package/dist/reflection.mjs +79 -0
- package/dist/schemas.d.mts +7 -0
- package/dist/schemas.mjs +12 -3
- package/dist/types/index.d.mts +1 -31
- package/dist/types/utilities.d.mts +9 -0
- package/dist/types/utilities.mjs +1 -0
- package/dist/types/website.d.mts +49 -58
- package/dist/xml/metadata.d.mts +16 -0
- package/dist/xml/metadata.mjs +32 -11
- package/dist/xml/schemas.d.mts +5970 -3
- package/dist/xml/schemas.mjs +43 -45
- package/dist/xml/types.d.mts +13 -30
- package/dist/xquery.d.mts +46 -0
- package/dist/xquery.mjs +66 -0
- package/package.json +3 -3
- package/dist/utilities.d.mts +0 -54
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { DEFAULT_LANGUAGES } from "../../constants.mjs";
|
|
2
|
+
import { getErrorOutput } from "../../errors.mjs";
|
|
3
|
+
import { requestOchre } from "../request.mjs";
|
|
3
4
|
import { MultilingualString } from "../../parsers/multilingual.mjs";
|
|
4
5
|
import { setPropertyValuesParametersSchema } from "../../schemas.mjs";
|
|
5
6
|
import { parseXMLContent } from "../../parsers/string.mjs";
|
|
6
|
-
import {
|
|
7
|
+
import { stringLiteral } from "../../xquery.mjs";
|
|
8
|
+
import { compileSetItemsQuery, getItemFilterQueries, getPropertyFacetSelectors } from "../../query.mjs";
|
|
7
9
|
import * as v from "valibot";
|
|
8
|
-
import { XMLParser } from "fast-xml-parser";
|
|
9
10
|
//#region src/fetchers/set/property-values.ts
|
|
10
11
|
function getLabelContentLanguages(content) {
|
|
11
12
|
const languages = [];
|
|
@@ -22,12 +23,16 @@ function parsePropertyValueLabel(content, text) {
|
|
|
22
23
|
if (parsedText == null) return null;
|
|
23
24
|
return MultilingualString.fromObject({ [DEFAULT_LANGUAGES[0]]: parsedText });
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The prefix OCHRE puts on every `dataType` attribute
|
|
28
|
+
*
|
|
29
|
+
* Kept as a constant because the facet XQuery has to strip the same prefix
|
|
30
|
+
* server-side: it compares `@dataType` against the unprefixed names, so a
|
|
31
|
+
* prefixed value falls through every typed branch.
|
|
32
|
+
*/
|
|
33
|
+
const XML_SCHEMA_TYPE_PREFIX = "xs:";
|
|
29
34
|
function normalizePropertyValueDataType(dataType) {
|
|
30
|
-
const normalizedDataType = dataType.startsWith(
|
|
35
|
+
const normalizedDataType = dataType.startsWith(XML_SCHEMA_TYPE_PREFIX) ? dataType.slice(3) : dataType;
|
|
31
36
|
switch (normalizedDataType) {
|
|
32
37
|
case "IDREF":
|
|
33
38
|
case "boolean":
|
|
@@ -68,42 +73,26 @@ const propertyValueLabelContentSchema = v.object({
|
|
|
68
73
|
lang: v.string(),
|
|
69
74
|
string: v.array(propertyValueLabelStringSchema)
|
|
70
75
|
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
continue;
|
|
76
|
+
/**
|
|
77
|
+
* Give the canonical value the type its `dataType` implies
|
|
78
|
+
*
|
|
79
|
+
* Which value a facet stands for is decided once, server-side, by
|
|
80
|
+
* `local:value-content`, and travels back as `canonicalValue`. This only gives
|
|
81
|
+
* that string a TypeScript type: re-deriving it here from `rawValue`, `uuid`
|
|
82
|
+
* and the label is what let the facet key and the facet content disagree.
|
|
83
|
+
*/
|
|
84
|
+
function typePropertyValueContent(dataType, canonicalValue) {
|
|
85
|
+
if (canonicalValue == null || canonicalValue === "") return null;
|
|
86
|
+
switch (dataType) {
|
|
87
|
+
case "integer":
|
|
88
|
+
case "decimal":
|
|
89
|
+
case "time": {
|
|
90
|
+
const numericContent = Number(canonicalValue);
|
|
91
|
+
return Number.isNaN(numericContent) ? null : numericContent;
|
|
88
92
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return propertyFacetSelectors.values().toArray();
|
|
92
|
-
}
|
|
93
|
-
function getItemFilterQueriesFromPropertyValueQueries(queries) {
|
|
94
|
-
if (queries == null) return null;
|
|
95
|
-
if ("target" in queries) {
|
|
96
|
-
if (queries.target !== "property" || queries.dataType === "date" || queries.dataType === "dateTime") return queries;
|
|
97
|
-
return "value" in queries && queries.value != null ? queries : null;
|
|
98
|
-
}
|
|
99
|
-
const filteredChildren = [];
|
|
100
|
-
const childQueries = "and" in queries ? queries.and : queries.or;
|
|
101
|
-
for (const childQuery of childQueries) {
|
|
102
|
-
const filteredChildQuery = getItemFilterQueriesFromPropertyValueQueries(childQuery);
|
|
103
|
-
if (filteredChildQuery != null) filteredChildren.push(filteredChildQuery);
|
|
93
|
+
case "boolean": return canonicalValue === "true";
|
|
94
|
+
default: return canonicalValue;
|
|
104
95
|
}
|
|
105
|
-
if (filteredChildren.length <= 1) return filteredChildren[0] ?? null;
|
|
106
|
-
return "and" in queries ? { and: filteredChildren } : { or: filteredChildren };
|
|
107
96
|
}
|
|
108
97
|
/**
|
|
109
98
|
* Schema for a single property value query item in the OCHRE API response
|
|
@@ -115,44 +104,22 @@ const propertyValueQueryItemSchema = v.pipe(v.object({
|
|
|
115
104
|
count: countSchema,
|
|
116
105
|
globalCount: v.nullish(countSchema),
|
|
117
106
|
dataType: v.optional(v.string(), "string"),
|
|
107
|
+
canonicalValue: v.optional(v.string()),
|
|
118
108
|
rawValue: v.optional(v.string()),
|
|
119
109
|
payload: v.optional(v.string()),
|
|
120
110
|
content: v.optional(v.array(propertyValueLabelContentSchema))
|
|
121
111
|
}), v.transform((value) => {
|
|
122
112
|
const dataType = normalizePropertyValueDataType(value.dataType);
|
|
123
|
-
|
|
124
|
-
const returnValue = {
|
|
113
|
+
return {
|
|
125
114
|
uuid: value.uuid !== "" ? value.uuid : null,
|
|
126
115
|
scope: value.scope,
|
|
127
116
|
variableUuid: value.variableUuid != null && value.variableUuid !== "" ? value.variableUuid : null,
|
|
128
117
|
count: value.count,
|
|
129
118
|
globalCount: value.globalCount ?? null,
|
|
130
119
|
dataType,
|
|
131
|
-
content:
|
|
132
|
-
label
|
|
120
|
+
content: typePropertyValueContent(dataType, value.canonicalValue),
|
|
121
|
+
label: parsePropertyValueLabel(value.content, value.payload)
|
|
133
122
|
};
|
|
134
|
-
switch (dataType) {
|
|
135
|
-
case "IDREF":
|
|
136
|
-
returnValue.content = value.uuid !== "" ? value.uuid : null;
|
|
137
|
-
break;
|
|
138
|
-
case "integer":
|
|
139
|
-
case "decimal":
|
|
140
|
-
case "time":
|
|
141
|
-
if (value.rawValue != null && value.rawValue !== "") {
|
|
142
|
-
const numericContent = Number(value.rawValue);
|
|
143
|
-
returnValue.content = Number.isNaN(numericContent) ? null : numericContent;
|
|
144
|
-
}
|
|
145
|
-
break;
|
|
146
|
-
case "boolean":
|
|
147
|
-
returnValue.content = parsePropertyValueBooleanContent(value.rawValue);
|
|
148
|
-
break;
|
|
149
|
-
default: {
|
|
150
|
-
const labelText = label?.getText() ?? null;
|
|
151
|
-
returnValue.content = value.rawValue != null && value.rawValue !== "" ? value.rawValue : labelText;
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return returnValue;
|
|
156
123
|
}));
|
|
157
124
|
const attributeValueQueryItemSchema = v.pipe(v.object({
|
|
158
125
|
attributeType: v.picklist(["bibliographies", "periods"]),
|
|
@@ -187,20 +154,9 @@ const responseSchema = v.object({ result: v.object({ ochre: v.object({
|
|
|
187
154
|
*/
|
|
188
155
|
function buildXQuery(parameters) {
|
|
189
156
|
const { setScopeUuids, belongsToCollectionScopeUuids, queries, propertyFacetSelectors, attributes, isLimitedToLeafPropertyValues } = parameters;
|
|
190
|
-
const setScopeDeclaration = `declare variable $setScopeUuids := (${setScopeUuids.map((uuid) => stringLiteral(uuid)).join(", ")});`;
|
|
191
|
-
const compiledQueryPlan = buildQueryPlan({
|
|
192
|
-
queries: getItemFilterQueriesFromPropertyValueQueries(queries),
|
|
193
|
-
baseItemsExpression: "doc()/ochre/set[@uuid = $setScopeUuids]/items/*",
|
|
194
|
-
scopeQueryExpression: buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, BELONGS_TO_COLLECTION_UUID)
|
|
195
|
-
});
|
|
196
157
|
const valueFilter = isLimitedToLeafPropertyValues ? "[not(@i)]" : "";
|
|
197
|
-
const queryBlocks = [];
|
|
198
158
|
const returnedSequences = [];
|
|
199
|
-
const xqueryDeclarations = [
|
|
200
|
-
"xquery version \"1.0-ml\";",
|
|
201
|
-
"declare namespace map = \"http://marklogic.com/xdmp/map\";",
|
|
202
|
-
setScopeDeclaration,
|
|
203
|
-
`declare function local:increment-count($counts, $key) {
|
|
159
|
+
const xqueryDeclarations = ["declare namespace map = \"http://marklogic.com/xdmp/map\";", `declare function local:increment-count($counts, $key) {
|
|
204
160
|
let $current := map:get($counts, $key)
|
|
205
161
|
return map:put(
|
|
206
162
|
$counts,
|
|
@@ -209,6 +165,12 @@ function buildXQuery(parameters) {
|
|
|
209
165
|
)
|
|
210
166
|
};
|
|
211
167
|
|
|
168
|
+
declare function local:normalize-data-type($data-type) {
|
|
169
|
+
if (starts-with($data-type, ${stringLiteral(XML_SCHEMA_TYPE_PREFIX)}))
|
|
170
|
+
then substring($data-type, 4)
|
|
171
|
+
else $data-type
|
|
172
|
+
};
|
|
173
|
+
|
|
212
174
|
declare function local:value-display-text($v) {
|
|
213
175
|
if ($v/content)
|
|
214
176
|
then string-join($v/content[@xml:lang="eng"]//text(), "")
|
|
@@ -257,6 +219,7 @@ declare function local:put-property-detail(
|
|
|
257
219
|
$value-uuid,
|
|
258
220
|
$raw-value,
|
|
259
221
|
$data-type,
|
|
222
|
+
$canonical-value,
|
|
260
223
|
$display,
|
|
261
224
|
$label-content
|
|
262
225
|
) {
|
|
@@ -274,7 +237,7 @@ declare function local:put-property-detail(
|
|
|
274
237
|
map:put(
|
|
275
238
|
$details,
|
|
276
239
|
$key,
|
|
277
|
-
<propertyValue scope="{$scope}" variableUuid="{$variable-uuid}" uuid="{$value-uuid}" rawValue="{$raw-value}" dataType="{$data-type}">{
|
|
240
|
+
<propertyValue scope="{$scope}" variableUuid="{$variable-uuid}" uuid="{$value-uuid}" rawValue="{$raw-value}" dataType="{$data-type}" canonicalValue="{$canonical-value}">{
|
|
278
241
|
if (exists($label-content)) then $label-content else $display
|
|
279
242
|
}</propertyValue>
|
|
280
243
|
)
|
|
@@ -291,6 +254,7 @@ declare function local:add-property-facet(
|
|
|
291
254
|
$value-uuid,
|
|
292
255
|
$raw-value,
|
|
293
256
|
$data-type,
|
|
257
|
+
$canonical-value,
|
|
294
258
|
$display,
|
|
295
259
|
$label-content
|
|
296
260
|
) {
|
|
@@ -298,7 +262,7 @@ declare function local:add-property-facet(
|
|
|
298
262
|
else (
|
|
299
263
|
map:put($seen, $key, true()),
|
|
300
264
|
local:increment-count($counts, $key),
|
|
301
|
-
local:put-property-detail($details, $key, $scope, $variable-uuid, $value-uuid, $raw-value, $data-type, $display, $label-content)
|
|
265
|
+
local:put-property-detail($details, $key, $scope, $variable-uuid, $value-uuid, $raw-value, $data-type, $canonical-value, $display, $label-content)
|
|
302
266
|
)
|
|
303
267
|
};
|
|
304
268
|
|
|
@@ -308,31 +272,32 @@ declare function local:add-attribute-facet($counts, $seen, $key) {
|
|
|
308
272
|
map:put($seen, $key, true()),
|
|
309
273
|
local:increment-count($counts, $key)
|
|
310
274
|
)
|
|
311
|
-
};`
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
275
|
+
};`];
|
|
276
|
+
function buildQueryBlocks(context) {
|
|
277
|
+
const { items, notSupplemental } = context;
|
|
278
|
+
const queryBlocks = [];
|
|
279
|
+
if (propertyFacetSelectors.length > 0) {
|
|
280
|
+
const facetPropertyPredicates = [];
|
|
281
|
+
for (const selector of propertyFacetSelectors) {
|
|
282
|
+
const uuidPredicate = `label/@uuid = ${stringLiteral(selector.uuid)}`;
|
|
283
|
+
facetPropertyPredicates.push(selector.relation == null ? uuidPredicate : `(${uuidPredicate} and label/@relation = ${stringLiteral(selector.relation)})`);
|
|
284
|
+
}
|
|
285
|
+
const facetPropertyPredicate = facetPropertyPredicates.length === 1 ? facetPropertyPredicates[0] ?? "false()" : `(${facetPropertyPredicates.join(" or ")})`;
|
|
286
|
+
queryBlocks.push(`let $global-property-counts := map:map()
|
|
322
287
|
let $variable-property-counts := map:map()
|
|
323
288
|
let $variable-property-details := map:map()
|
|
324
289
|
let $variable-property-global-keys := map:map()
|
|
325
290
|
let $_property-aggregation := xdmp:eager(
|
|
326
|
-
for $item in $items
|
|
291
|
+
for $item in ${items}
|
|
327
292
|
let $global-seen := map:map()
|
|
328
293
|
let $variable-seen := map:map()
|
|
329
294
|
return
|
|
330
|
-
for $p in $item/properties/property[${facetPropertyPredicate}]${
|
|
295
|
+
for $p in $item/properties/property[${facetPropertyPredicate}]${notSupplemental}
|
|
331
296
|
let $variable-uuid := string($p/label/@uuid)
|
|
332
|
-
for $v in $p/value${valueFilter}${
|
|
297
|
+
for $v in $p/value${valueFilter}${notSupplemental}
|
|
333
298
|
let $value-uuid := string($v/@uuid)
|
|
334
299
|
let $raw-value := string($v/@rawValue)
|
|
335
|
-
let $data-type := string($v/@dataType)
|
|
300
|
+
let $data-type := local:normalize-data-type(string($v/@dataType))
|
|
336
301
|
let $display := local:value-display-text($v)
|
|
337
302
|
let $label-content := local:value-label-content($v)
|
|
338
303
|
let $content := local:value-content($data-type, $raw-value, $value-uuid, $display)
|
|
@@ -343,7 +308,7 @@ let $_property-aggregation := xdmp:eager(
|
|
|
343
308
|
where $content != ""
|
|
344
309
|
return (
|
|
345
310
|
local:add-attribute-facet($global-property-counts, $global-seen, $global-key),
|
|
346
|
-
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),
|
|
311
|
+
local:add-property-facet($variable-property-counts, $variable-property-details, $variable-seen, $variable-key, "variable", $variable-uuid, $value-uuid, $output-raw-value, $data-type, $content, $display, $label-content),
|
|
347
312
|
map:put($variable-property-global-keys, $variable-key, $global-key)
|
|
348
313
|
)
|
|
349
314
|
)
|
|
@@ -354,19 +319,19 @@ let $property-values :=
|
|
|
354
319
|
for $key in map:keys($variable-property-counts)
|
|
355
320
|
let $detail := map:get($variable-property-details, $key)
|
|
356
321
|
let $global-key := map:get($variable-property-global-keys, $key)
|
|
357
|
-
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)}">{
|
|
322
|
+
return <propertyValue scope="variable" variableUuid="{string($detail/@variableUuid)}" uuid="{string($detail/@uuid)}" rawValue="{string($detail/@rawValue)}" dataType="{string($detail/@dataType)}" canonicalValue="{string($detail/@canonicalValue)}" count="{map:get($variable-property-counts, $key)}" globalCount="{map:get($global-property-counts, $global-key)}">{
|
|
358
323
|
$detail/node()
|
|
359
324
|
}</propertyValue>
|
|
360
325
|
)`);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
326
|
+
returnedSequences.push("$property-values");
|
|
327
|
+
}
|
|
328
|
+
if (attributes.bibliographies) {
|
|
329
|
+
queryBlocks.push(`let $bibliography-counts := map:map()
|
|
365
330
|
let $_bibliography-aggregation := xdmp:eager(
|
|
366
|
-
for $item in $items
|
|
331
|
+
for $item in ${items}
|
|
367
332
|
let $seen := map:map()
|
|
368
333
|
return
|
|
369
|
-
for $bibliography in $item/bibliographies/bibliography${
|
|
334
|
+
for $bibliography in $item/bibliographies/bibliography${notSupplemental}
|
|
370
335
|
let $label := string-join($bibliography/identification/label/content[@xml:lang="eng"]//text(), "")
|
|
371
336
|
where string-length($label) gt 0
|
|
372
337
|
return local:add-attribute-facet($bibliography-counts, $seen, $label)
|
|
@@ -378,15 +343,15 @@ let $bibliography-values :=
|
|
|
378
343
|
for $label in map:keys($bibliography-counts)
|
|
379
344
|
return <attributeValue attributeType="bibliographies" count="{map:get($bibliography-counts, $label)}" content="{$label}" />
|
|
380
345
|
)`);
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
346
|
+
returnedSequences.push("$bibliography-values");
|
|
347
|
+
}
|
|
348
|
+
if (attributes.periods) {
|
|
349
|
+
queryBlocks.push(`let $period-counts := map:map()
|
|
385
350
|
let $_period-aggregation := xdmp:eager(
|
|
386
|
-
for $item in $items
|
|
351
|
+
for $item in ${items}
|
|
387
352
|
let $seen := map:map()
|
|
388
353
|
return
|
|
389
|
-
for $period in $item/periods/period${
|
|
354
|
+
for $period in $item/periods/period${notSupplemental}
|
|
390
355
|
let $label := string-join($period/identification/label/content[@xml:lang="eng"]//text(), "")
|
|
391
356
|
where string-length($label) gt 0
|
|
392
357
|
return local:add-attribute-facet($period-counts, $seen, $label)
|
|
@@ -398,16 +363,21 @@ let $period-values :=
|
|
|
398
363
|
for $label in map:keys($period-counts)
|
|
399
364
|
return <attributeValue attributeType="periods" count="{map:get($period-counts, $label)}" content="{$label}" />
|
|
400
365
|
)`);
|
|
401
|
-
|
|
366
|
+
returnedSequences.push("$period-values");
|
|
367
|
+
}
|
|
368
|
+
return queryBlocks;
|
|
402
369
|
}
|
|
403
|
-
return
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
370
|
+
return compileSetItemsQuery({
|
|
371
|
+
setScopeUuids,
|
|
372
|
+
belongsToCollectionScopeUuids,
|
|
373
|
+
queries: getItemFilterQueries(queries),
|
|
374
|
+
declarations: xqueryDeclarations,
|
|
375
|
+
body: (context) => {
|
|
376
|
+
return `${buildQueryBlocks(context).join("\n\n")}
|
|
408
377
|
|
|
409
|
-
return (${returnedSequences.join(", ")})
|
|
410
|
-
}
|
|
378
|
+
return (${returnedSequences.join(", ")})`;
|
|
379
|
+
}
|
|
380
|
+
});
|
|
411
381
|
}
|
|
412
382
|
function collectPropertyValues(parsedPropertyValues) {
|
|
413
383
|
const propertyValuesByPropertyVariableUuid = {};
|
|
@@ -476,7 +446,7 @@ function collectAttributeValues(parsedAttributeValues) {
|
|
|
476
446
|
async function fetchSetPropertyValues(parameters, options) {
|
|
477
447
|
try {
|
|
478
448
|
const { setScopeUuids, belongsToCollectionScopeUuids, queries, attributes, isLimitedToLeafPropertyValues } = v.parse(setPropertyValuesParametersSchema, parameters);
|
|
479
|
-
const propertyFacetSelectors =
|
|
449
|
+
const propertyFacetSelectors = getPropertyFacetSelectors(queries);
|
|
480
450
|
if (propertyFacetSelectors.length === 0 && !attributes.bibliographies && !attributes.periods) return {
|
|
481
451
|
propertyValues: [],
|
|
482
452
|
propertyValuesByPropertyVariableUuid: {},
|
|
@@ -495,16 +465,12 @@ async function fetchSetPropertyValues(parameters, options) {
|
|
|
495
465
|
attributes,
|
|
496
466
|
isLimitedToLeafPropertyValues
|
|
497
467
|
});
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
468
|
+
const output = await requestOchre({
|
|
469
|
+
xquery,
|
|
470
|
+
schema: responseSchema,
|
|
471
|
+
label: "OCHRE Set property values",
|
|
472
|
+
options
|
|
502
473
|
});
|
|
503
|
-
if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`, { cause: response.statusText });
|
|
504
|
-
const dataRaw = await response.text();
|
|
505
|
-
const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
|
|
506
|
-
const { success, issues, output } = v.safeParse(responseSchema, data);
|
|
507
|
-
if (!success) throw createSchemaValidationError("Failed to parse OCHRE Set property values", issues);
|
|
508
474
|
const parsedPropertyValues = [output.result.ochre.propertyValue ?? []].flat();
|
|
509
475
|
const parsedAttributeValues = [output.result.ochre.attributeValue ?? []].flat();
|
|
510
476
|
const { propertyValuesByPropertyVariableUuid, flattenedPropertyValues } = collectPropertyValues(parsedPropertyValues);
|
|
@@ -1,14 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { iso639_3Schema } from "../schemas.mjs";
|
|
4
|
-
import { restoreXMLMetadata } from "../xml/metadata.mjs";
|
|
1
|
+
import { getErrorOutput } from "../errors.mjs";
|
|
2
|
+
import { requestOchre } from "./request.mjs";
|
|
5
3
|
import { parseStringLike } from "../parsers/helpers.mjs";
|
|
6
|
-
import {
|
|
4
|
+
import { parseMetadataLanguages, parseRequestedLanguages, resolveLanguages } from "../parsers/languages.mjs";
|
|
5
|
+
import { parseIdentification, parseSimplifiedProperties } from "../parsers/index.mjs";
|
|
7
6
|
import { XMLWebsiteData } from "../xml/schemas.mjs";
|
|
7
|
+
import { compileOchreQuery, stringLiteral } from "../xquery.mjs";
|
|
8
8
|
import { websitePresentationReader } from "../parsers/website/reader.mjs";
|
|
9
|
-
import
|
|
10
|
-
import { XMLParser } from "fast-xml-parser";
|
|
9
|
+
import { WEBSITE_WALK_DECLARATIONS } from "../parsers/website/walk.mjs";
|
|
11
10
|
//#region src/fetchers/website-metadata.ts
|
|
11
|
+
/**
|
|
12
|
+
* The presentation properties the metadata projection carries
|
|
13
|
+
*
|
|
14
|
+
* The XQuery whitelists exactly these labels and the parser reads exactly
|
|
15
|
+
* these labels, so they come from one list: whitelisting a label the parser
|
|
16
|
+
* ignores is dead weight, and reading one the whitelist drops silently yields
|
|
17
|
+
* the default.
|
|
18
|
+
*/
|
|
19
|
+
const METADATA_PRESENTATION_LABELS = {
|
|
20
|
+
privacy: "privacy",
|
|
21
|
+
faviconIco: "favicon-ico",
|
|
22
|
+
faviconImg: "favicon-img"
|
|
23
|
+
};
|
|
12
24
|
function parseWebsiteMetadata(data, options) {
|
|
13
25
|
const rawOchre = data.result.ochre;
|
|
14
26
|
const websiteTree = rawOchre.tree[0];
|
|
@@ -30,52 +42,21 @@ function parseWebsiteMetadata(data, options) {
|
|
|
30
42
|
description: websiteName === "" ? metadataDescription : metadataDescription !== "" && metadataDescription !== websiteName ? `${websiteName} - ${metadataDescription}` : websiteName,
|
|
31
43
|
webpageTitle,
|
|
32
44
|
properties: {
|
|
33
|
-
privacy: reader.valueOr(
|
|
45
|
+
privacy: reader.valueOr(METADATA_PRESENTATION_LABELS.privacy, "public"),
|
|
34
46
|
icon: {
|
|
35
|
-
faviconUuid: reader.uuid(
|
|
36
|
-
appleTouchIconUuid: reader.uuid(
|
|
47
|
+
faviconUuid: reader.uuid(METADATA_PRESENTATION_LABELS.faviconIco),
|
|
48
|
+
appleTouchIconUuid: reader.uuid(METADATA_PRESENTATION_LABELS.faviconImg)
|
|
37
49
|
}
|
|
38
50
|
}
|
|
39
51
|
};
|
|
40
52
|
}
|
|
41
53
|
function buildXQuery(parameters) {
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
${SUPPLEMENTAL_XQUERY_PROLOG}
|
|
45
|
-
|
|
46
|
-
declare function local:resource-items($resources) {
|
|
47
|
-
for $resource in $resources
|
|
48
|
-
return
|
|
49
|
-
if ($resource/segments) then $resource
|
|
50
|
-
else if ($resource/identification) then $resource
|
|
51
|
-
else local:resource-items($resource/resource)
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
declare function local:presentation($resource) {
|
|
55
|
-
string(($resource/properties/property[label/string() = "presentation"]/value)[1])
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
declare function local:clean-slug($slug) {
|
|
59
|
-
replace(string($slug), "^\$[^-]*-", "")
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
declare function local:page-slug($resource, $slug-prefix) {
|
|
63
|
-
let $slug := local:clean-slug($resource/@slug)
|
|
64
|
-
return
|
|
65
|
-
if ($slug-prefix = "") then $slug
|
|
66
|
-
else if ($slug = "") then $slug-prefix
|
|
67
|
-
else concat($slug-prefix, "/", $slug)
|
|
68
|
-
};
|
|
69
|
-
|
|
54
|
+
return compileOchreQuery({
|
|
55
|
+
declarations: [`${WEBSITE_WALK_DECLARATIONS}
|
|
70
56
|
declare function local:matches-page-slug($resource, $target-slug, $slug-prefix) {
|
|
71
57
|
local:page-slug($resource, $slug-prefix) = $target-slug
|
|
72
58
|
};
|
|
73
59
|
|
|
74
|
-
declare function local:page-child-slug-prefix($resource, $slug-prefix) {
|
|
75
|
-
if ($slug-prefix = "") then ""
|
|
76
|
-
else local:page-slug($resource, $slug-prefix)
|
|
77
|
-
};
|
|
78
|
-
|
|
79
60
|
declare function local:metadata-page($resource) {
|
|
80
61
|
element resource {
|
|
81
62
|
attribute uuid { string($resource/@uuid) },
|
|
@@ -108,7 +89,7 @@ declare function local:matching-pages($resources, $target-slug, $slug-prefix) {
|
|
|
108
89
|
|
|
109
90
|
declare function local:metadata-tree($tree, $target-slug, $slug-prefix) {
|
|
110
91
|
let $resources := local:resource-items($tree/items/resource)
|
|
111
|
-
let $presentation-properties := $tree/properties/property[label/string() = "presentation"][value/string() = "website"]/property[label/string() = (
|
|
92
|
+
let $presentation-properties := $tree/properties/property[label/string() = "presentation"][value/string() = "website"]/property[label/string() = (${Object.values(METADATA_PRESENTATION_LABELS).map((label) => stringLiteral(label)).join(", ")})]
|
|
112
93
|
return
|
|
113
94
|
element tree {
|
|
114
95
|
attribute uuid { string($tree/@uuid) },
|
|
@@ -124,7 +105,8 @@ declare function local:metadata-tree($tree, $target-slug, $slug-prefix) {
|
|
|
124
105
|
}
|
|
125
106
|
};
|
|
126
107
|
|
|
127
|
-
|
|
108
|
+
`],
|
|
109
|
+
body: ({ omitSupplemental }) => `let $website := collection("ochre/tree")/ochre[tree/identification/abbreviation/content/string = ${stringLiteral(parameters.abbreviation)}][1]
|
|
128
110
|
let $target-slug := ${stringLiteral(parameters.slug)}
|
|
129
111
|
return
|
|
130
112
|
<ochre>{
|
|
@@ -137,28 +119,24 @@ return
|
|
|
137
119
|
$website/metadata,
|
|
138
120
|
local:metadata-tree($website/tree[1], $target-slug, "")
|
|
139
121
|
)`)}
|
|
140
|
-
}</ochre
|
|
122
|
+
}</ochre>`
|
|
123
|
+
});
|
|
141
124
|
}
|
|
142
125
|
async function fetchWebsiteMetadata(abbreviation, options) {
|
|
143
126
|
try {
|
|
144
127
|
if (options?.slug == null) throw new Error("Website metadata slug is required");
|
|
145
128
|
const cleanAbbreviation = abbreviation.trim().toLocaleLowerCase("en-US");
|
|
146
129
|
const slug = options.slug.trim().replaceAll(/^\/+|\/+$/g, "");
|
|
147
|
-
const requestedLanguages =
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
body: buildXQuery({
|
|
130
|
+
const requestedLanguages = parseRequestedLanguages(options.languages);
|
|
131
|
+
const output = await requestOchre({
|
|
132
|
+
xquery: buildXQuery({
|
|
151
133
|
abbreviation: cleanAbbreviation,
|
|
152
134
|
slug
|
|
153
135
|
}),
|
|
154
|
-
|
|
136
|
+
schema: XMLWebsiteData,
|
|
137
|
+
label: "OCHRE website metadata",
|
|
138
|
+
options
|
|
155
139
|
});
|
|
156
|
-
if (!response.ok) throw new Error("Failed to fetch website metadata", { cause: response.statusText });
|
|
157
|
-
const dataRaw = await response.text();
|
|
158
|
-
const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
|
|
159
|
-
const { success, issues, output } = v.safeParse(XMLWebsiteData, data);
|
|
160
|
-
if (!success) throw createSchemaValidationError("Failed to parse website metadata XML", issues);
|
|
161
|
-
restoreXMLMetadata(output, data);
|
|
162
140
|
const metadataLanguages = parseMetadataLanguages(output.result.ochre);
|
|
163
141
|
return {
|
|
164
142
|
websiteMetadata: parseWebsiteMetadata(output, { languages: resolveLanguages(requestedLanguages, metadataLanguages) }),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ProtectedWebsite, Website } from "../types/website.mjs";
|
|
2
2
|
import { LanguageCodes } from "../types/index.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { OchreRequestOptions } from "./request.mjs";
|
|
4
4
|
//#region src/fetchers/website.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Fetches and parses a website configuration from the OCHRE API.
|
|
@@ -11,8 +11,7 @@ import { FetchFunction } from "../parsers/helpers.mjs";
|
|
|
11
11
|
* `username` and `password` for OCHRE accounts) to authenticate and receive the full
|
|
12
12
|
* website data.
|
|
13
13
|
*/
|
|
14
|
-
export declare function fetchWebsite<const T extends LanguageCodes = LanguageCodes>(abbreviation: string, options?: {
|
|
15
|
-
fetch?: FetchFunction;
|
|
14
|
+
export declare function fetchWebsite<const T extends LanguageCodes = LanguageCodes>(abbreviation: string, options?: OchreRequestOptions & {
|
|
16
15
|
languages?: T;
|
|
17
16
|
credentials?: string | {
|
|
18
17
|
username: string;
|
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { getErrorOutput } from "../errors.mjs";
|
|
2
|
+
import { isOchreJsonAccepted, requestOchre } from "./request.mjs";
|
|
3
|
+
import { parseLanguages } from "../parsers/languages.mjs";
|
|
4
4
|
import { XMLWebsiteData } from "../xml/schemas.mjs";
|
|
5
|
+
import { compileOchreQuery, stringLiteral } from "../xquery.mjs";
|
|
5
6
|
import { parseWebsite } from "../parsers/website/index.mjs";
|
|
6
|
-
import * as v from "valibot";
|
|
7
|
-
import { XMLParser } from "fast-xml-parser";
|
|
8
7
|
//#region src/fetchers/website.ts
|
|
9
|
-
async function areWebsiteCredentialsValid(uuid, credentials,
|
|
8
|
+
async function areWebsiteCredentialsValid(uuid, credentials, options) {
|
|
10
9
|
const security = typeof credentials === "string" ? { validate: credentials } : {
|
|
11
10
|
validate: credentials.password,
|
|
12
11
|
userOCHRE: credentials.username
|
|
13
12
|
};
|
|
14
|
-
return (
|
|
15
|
-
|
|
16
|
-
body: JSON.stringify({
|
|
13
|
+
return isOchreJsonAccepted({
|
|
14
|
+
body: {
|
|
17
15
|
uuid,
|
|
18
16
|
data: { security }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
},
|
|
18
|
+
label: "website credentials",
|
|
19
|
+
options
|
|
20
|
+
});
|
|
22
21
|
}
|
|
23
22
|
/**
|
|
24
23
|
* Build an XQuery string to fetch a website tree document by abbreviation.
|
|
@@ -27,12 +26,8 @@ async function areWebsiteCredentialsValid(uuid, credentials, fetcher) {
|
|
|
27
26
|
* @returns An XQuery string
|
|
28
27
|
*/
|
|
29
28
|
function buildXQuery(abbreviation) {
|
|
30
|
-
return `
|
|
31
|
-
|
|
32
|
-
${SUPPLEMENTAL_XQUERY_PROLOG}
|
|
33
|
-
|
|
34
|
-
for $ochre in collection("ochre/tree")/ochre[tree/identification/abbreviation/content/string = ${stringLiteral(abbreviation)}]
|
|
35
|
-
return element ochre { $ochre/@*, ${omitSupplemental("$ochre/node()")} }`;
|
|
29
|
+
return compileOchreQuery({ body: ({ omitSupplemental }) => `for $ochre in collection("ochre/tree")/ochre[tree/identification/abbreviation/content/string = ${stringLiteral(abbreviation)}]
|
|
30
|
+
return element ochre { $ochre/@*, ${omitSupplemental("$ochre/node()")} }` });
|
|
36
31
|
}
|
|
37
32
|
/**
|
|
38
33
|
* Fetches and parses a website configuration from the OCHRE API.
|
|
@@ -45,19 +40,15 @@ return element ochre { $ochre/@*, ${omitSupplemental("$ochre/node()")} }`;
|
|
|
45
40
|
*/
|
|
46
41
|
async function fetchWebsite(abbreviation, options) {
|
|
47
42
|
try {
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
const cleanAbbreviation = abbreviation.trim().toLocaleLowerCase("en-US");
|
|
44
|
+
const languages = options?.languages == null ? void 0 : parseLanguages(options.languages);
|
|
45
|
+
const output = await requestOchre({
|
|
46
|
+
xquery: buildXQuery(cleanAbbreviation),
|
|
47
|
+
schema: XMLWebsiteData,
|
|
48
|
+
label: "OCHRE website",
|
|
49
|
+
options
|
|
53
50
|
});
|
|
54
|
-
|
|
55
|
-
const dataRaw = await response.text();
|
|
56
|
-
const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
|
|
57
|
-
const { success, issues, output } = v.safeParse(XMLWebsiteData, data);
|
|
58
|
-
if (!success) throw createSchemaValidationError("Failed to parse website XML", issues);
|
|
59
|
-
restoreXMLMetadata(output, data);
|
|
60
|
-
const website = parseWebsite(output, { languages: options?.languages });
|
|
51
|
+
const website = parseWebsite(output, { languages });
|
|
61
52
|
if (website.properties.privacy !== "public") {
|
|
62
53
|
if (options?.credentials == null) return {
|
|
63
54
|
website: null,
|
|
@@ -69,7 +60,7 @@ async function fetchWebsite(abbreviation, options) {
|
|
|
69
60
|
error: null,
|
|
70
61
|
detailedError: null
|
|
71
62
|
};
|
|
72
|
-
if (!await areWebsiteCredentialsValid(website.uuid, options.credentials,
|
|
63
|
+
if (!await areWebsiteCredentialsValid(website.uuid, options.credentials, options)) throw new Error("Invalid credentials for protected website");
|
|
73
64
|
}
|
|
74
65
|
return {
|
|
75
66
|
website,
|