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,17 +1,21 @@
|
|
|
1
|
+
import { isObject, readEntries } from "./reflection.mjs";
|
|
1
2
|
import * as v from "valibot";
|
|
2
|
-
//#region src/
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
//#region src/errors.ts
|
|
4
|
+
/**
|
|
5
|
+
* The shape a valibot issue has to have for this module to render it
|
|
6
|
+
*
|
|
7
|
+
* A schema rather than a hand-written predicate: the fields being checked are
|
|
8
|
+
* exactly a shape, and `v.is` narrows without asserting anything unchecked. An
|
|
9
|
+
* array cannot pass, because it carries none of these fields.
|
|
10
|
+
*/
|
|
11
|
+
const schemaValidationIssueSchema = v.looseObject({
|
|
12
|
+
kind: v.string(),
|
|
13
|
+
type: v.string(),
|
|
14
|
+
message: v.string()
|
|
15
|
+
});
|
|
16
|
+
const schemaValidationIssuesSchema = v.pipe(v.array(schemaValidationIssueSchema), v.minLength(1));
|
|
11
17
|
function isSchemaValidationIssues(value) {
|
|
12
|
-
|
|
13
|
-
for (const item of value) if (!isSchemaValidationIssue(item)) return false;
|
|
14
|
-
return true;
|
|
18
|
+
return v.is(schemaValidationIssuesSchema, value);
|
|
15
19
|
}
|
|
16
20
|
function getIssuePath(issue) {
|
|
17
21
|
const path = v.getDotPath(issue);
|
|
@@ -54,7 +58,7 @@ function formatCauseArray(value) {
|
|
|
54
58
|
}
|
|
55
59
|
function formatCauseRecord(value) {
|
|
56
60
|
const values = [];
|
|
57
|
-
for (const [key, entryValue] of
|
|
61
|
+
for (const [key, entryValue] of readEntries(value)) {
|
|
58
62
|
const formattedEntryValue = formatPrimitiveValue(entryValue);
|
|
59
63
|
if (formattedEntryValue != null) values.push(`${key}: ${formattedEntryValue}`);
|
|
60
64
|
}
|
|
@@ -65,7 +69,7 @@ function formatCauseValue(value) {
|
|
|
65
69
|
const primitiveValue = formatPrimitiveValue(value);
|
|
66
70
|
if (primitiveValue != null) return primitiveValue;
|
|
67
71
|
if (Array.isArray(value)) return formatCauseArray(value);
|
|
68
|
-
if (
|
|
72
|
+
if (isObject(value)) return formatCauseRecord(value);
|
|
69
73
|
return null;
|
|
70
74
|
}
|
|
71
75
|
function appendContainedErrors(lines, containedErrors, indent, depth, seenErrors) {
|
|
@@ -126,6 +130,13 @@ function getDetailedError(error, fallbackMessage = "Unknown error") {
|
|
|
126
130
|
appendDetailedError(lines, error, fallbackMessage, 0, /* @__PURE__ */ new Set());
|
|
127
131
|
return lines.join("\n");
|
|
128
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Render an error as a short message and a fully detailed report
|
|
135
|
+
* @param error - The thrown value
|
|
136
|
+
* @param fallbackMessage - The message to use when the value is not an Error
|
|
137
|
+
* @returns The message and the detailed report
|
|
138
|
+
* @internal
|
|
139
|
+
*/
|
|
129
140
|
function getErrorOutput(error, fallbackMessage) {
|
|
130
141
|
const message = getErrorMessage(error, fallbackMessage);
|
|
131
142
|
return {
|
|
@@ -133,6 +144,13 @@ function getErrorOutput(error, fallbackMessage) {
|
|
|
133
144
|
detailedError: getDetailedError(error, message)
|
|
134
145
|
};
|
|
135
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Wrap schema validation issues in an error {@link getErrorOutput} can render
|
|
149
|
+
* @param message - The failure message
|
|
150
|
+
* @param issues - The validation issues to carry as the cause
|
|
151
|
+
* @returns The error
|
|
152
|
+
* @internal
|
|
153
|
+
*/
|
|
136
154
|
function createSchemaValidationError(message, issues) {
|
|
137
155
|
return new Error(message, { cause: issues });
|
|
138
156
|
}
|
|
@@ -142,74 +160,5 @@ function createSchemaValidationError(message, issues) {
|
|
|
142
160
|
* @returns True if the string is a valid pseudo-UUID, false otherwise
|
|
143
161
|
* @internal
|
|
144
162
|
*/
|
|
145
|
-
function isPseudoUuid(value) {
|
|
146
|
-
return PSEUDO_UUID_REGEX.test(value);
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Build a string literal for an XQuery string
|
|
150
|
-
* @param value - The string value to escape
|
|
151
|
-
* @returns The escaped string literal
|
|
152
|
-
*/
|
|
153
|
-
function stringLiteral(value) {
|
|
154
|
-
return `"${value.replaceAll("\"", "\"\"")}"`;
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* XQuery prolog declaring `local:omit-supplemental`, which drops every element
|
|
158
|
-
* carrying `supplemental="true"` from a node sequence, at any depth.
|
|
159
|
-
*
|
|
160
|
-
* Subtrees without a supplemental descendant are returned by reference, so
|
|
161
|
-
* nodes are only copied along the path leading to an omitted element. The
|
|
162
|
-
* lookahead walks the attribute axis (`//@supplemental`) rather than testing
|
|
163
|
-
* every element, which measures around three times faster on large documents.
|
|
164
|
-
*
|
|
165
|
-
* Must be declared before any query body that calls {@link omitSupplemental}.
|
|
166
|
-
*/
|
|
167
|
-
const SUPPLEMENTAL_XQUERY_PROLOG = `declare function local:omit-supplemental($nodes as node()*) as node()* {
|
|
168
|
-
for $node in $nodes
|
|
169
|
-
return
|
|
170
|
-
if ($node instance of element())
|
|
171
|
-
then
|
|
172
|
-
if ($node/@supplemental = "true")
|
|
173
|
-
then ()
|
|
174
|
-
else if (empty($node//@supplemental[. = "true"]))
|
|
175
|
-
then $node
|
|
176
|
-
else element { node-name($node) } {
|
|
177
|
-
$node/@*,
|
|
178
|
-
local:omit-supplemental($node/node())
|
|
179
|
-
}
|
|
180
|
-
else $node
|
|
181
|
-
};`;
|
|
182
|
-
/**
|
|
183
|
-
* Wrap an XQuery node expression so supplemental nodes are omitted from it
|
|
184
|
-
* @param expression - The XQuery expression returning the nodes to filter
|
|
185
|
-
* @returns The wrapped XQuery expression
|
|
186
|
-
*/
|
|
187
|
-
function omitSupplemental(expression) {
|
|
188
|
-
return `local:omit-supplemental(${expression})`;
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* XQuery predicate keeping only nodes that are neither supplemental themselves
|
|
192
|
-
* nor nested inside a supplemental node. Use it when aggregating over nodes
|
|
193
|
-
* instead of returning them.
|
|
194
|
-
*/
|
|
195
|
-
const NOT_SUPPLEMENTAL_PREDICATE = "[not(ancestor-or-self::*[@supplemental = \"true\"])]";
|
|
196
|
-
/**
|
|
197
|
-
* Flatten a properties array
|
|
198
|
-
* @param properties - The properties to flatten
|
|
199
|
-
* @returns The flattened properties
|
|
200
|
-
* @internal
|
|
201
|
-
*/
|
|
202
|
-
function flattenProperties(properties) {
|
|
203
|
-
const result = [];
|
|
204
|
-
for (const property of properties) {
|
|
205
|
-
result.push({
|
|
206
|
-
variable: property.variable,
|
|
207
|
-
values: property.values,
|
|
208
|
-
comment: property.comment
|
|
209
|
-
});
|
|
210
|
-
if ("properties" in property) result.push(...flattenProperties(property.properties));
|
|
211
|
-
}
|
|
212
|
-
return result;
|
|
213
|
-
}
|
|
214
163
|
//#endregion
|
|
215
|
-
export {
|
|
164
|
+
export { createSchemaValidationError, getErrorOutput };
|
|
@@ -1,48 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { gallerySchema
|
|
4
|
-
import {
|
|
1
|
+
import { getErrorOutput } from "../errors.mjs";
|
|
2
|
+
import { requestOchre } from "./request.mjs";
|
|
3
|
+
import { gallerySchema } from "../schemas.mjs";
|
|
4
|
+
import { parseRequestedLanguages, resolveContentLanguages } from "../parsers/languages.mjs";
|
|
5
5
|
import { parseGallery } from "../parsers/index.mjs";
|
|
6
6
|
import { XMLGalleryData } from "../xml/schemas.mjs";
|
|
7
|
+
import { compileOchreQuery, stringLiteral } from "../xquery.mjs";
|
|
7
8
|
import * as v from "valibot";
|
|
8
|
-
import { XMLParser } from "fast-xml-parser";
|
|
9
9
|
//#region src/fetchers/gallery.ts
|
|
10
|
-
function parseLanguages(languages) {
|
|
11
|
-
for (const language of languages) v.parse(iso639_3Schema, language);
|
|
12
|
-
return languages;
|
|
13
|
-
}
|
|
14
|
-
function isRecord(value) {
|
|
15
|
-
return typeof value === "object" && value !== null;
|
|
16
|
-
}
|
|
17
|
-
function collectContentLanguages(value, languages) {
|
|
18
|
-
if (Array.isArray(value)) {
|
|
19
|
-
for (const item of value) collectContentLanguages(item, languages);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
if (!isRecord(value)) return;
|
|
23
|
-
const content = value.content;
|
|
24
|
-
if (Array.isArray(content)) for (const contentItem of content) {
|
|
25
|
-
if (!isRecord(contentItem)) continue;
|
|
26
|
-
const language = contentItem.lang;
|
|
27
|
-
if (typeof language === "string" && language !== "zxx") languages.add(language);
|
|
28
|
-
}
|
|
29
|
-
for (const child of Object.values(value)) collectContentLanguages(child, languages);
|
|
30
|
-
}
|
|
31
|
-
function resolveGalleryLanguages(data, requestedLanguages) {
|
|
32
|
-
if (requestedLanguages.length > 0) return requestedLanguages;
|
|
33
|
-
const languages = /* @__PURE__ */ new Set();
|
|
34
|
-
collectContentLanguages(data.result.ochre.gallery, languages);
|
|
35
|
-
return languages.size > 0 ? [...languages] : [...DEFAULT_LANGUAGES];
|
|
36
|
-
}
|
|
37
10
|
function buildXQuery(parameters) {
|
|
38
11
|
const { uuid, filter, page, perPage } = parameters;
|
|
39
12
|
const start = (page - 1) * perPage + 1;
|
|
40
13
|
const filterLiteral = stringLiteral(filter?.trim() ?? "");
|
|
41
|
-
return
|
|
42
|
-
|
|
43
|
-
${SUPPLEMENTAL_XQUERY_PROLOG}
|
|
44
|
-
|
|
45
|
-
<ochre>{
|
|
14
|
+
return compileOchreQuery({ body: ({ omitSupplemental }) => `<ochre>{
|
|
46
15
|
for $q in doc()/ochre[@uuid=${stringLiteral(uuid)}]
|
|
47
16
|
let $filter := ${filterLiteral}
|
|
48
17
|
let $resources := $q//items/resource
|
|
@@ -58,29 +27,24 @@ ${SUPPLEMENTAL_XQUERY_PROLOG}
|
|
|
58
27
|
subsequence($filtered, ${start}, ${perPage})
|
|
59
28
|
)`)}
|
|
60
29
|
}</gallery>
|
|
61
|
-
}</ochre
|
|
30
|
+
}</ochre>` });
|
|
62
31
|
}
|
|
63
32
|
async function fetchGallery(parameters, options) {
|
|
64
33
|
try {
|
|
65
34
|
const { uuid, filter, page, perPage } = v.parse(gallerySchema, parameters);
|
|
66
|
-
const requestedLanguages = options?.languages
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
body: buildXQuery({
|
|
35
|
+
const requestedLanguages = parseRequestedLanguages(options?.languages);
|
|
36
|
+
const output = await requestOchre({
|
|
37
|
+
xquery: buildXQuery({
|
|
70
38
|
uuid,
|
|
71
39
|
filter,
|
|
72
40
|
page,
|
|
73
41
|
perPage
|
|
74
42
|
}),
|
|
75
|
-
|
|
43
|
+
schema: XMLGalleryData,
|
|
44
|
+
label: "OCHRE gallery",
|
|
45
|
+
options
|
|
76
46
|
});
|
|
77
|
-
|
|
78
|
-
const dataRaw = await response.text();
|
|
79
|
-
const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
|
|
80
|
-
const { success, issues, output } = v.safeParse(XMLGalleryData, data);
|
|
81
|
-
if (!success) throw createSchemaValidationError("Failed to parse gallery XML", issues);
|
|
82
|
-
restoreXMLMetadata(output, data);
|
|
83
|
-
const languages = resolveGalleryLanguages(output, requestedLanguages);
|
|
47
|
+
const languages = resolveContentLanguages(output.result.ochre.gallery, requestedLanguages);
|
|
84
48
|
return {
|
|
85
49
|
gallery: parseGallery(output, { languages }),
|
|
86
50
|
error: null,
|
|
@@ -89,7 +53,7 @@ async function fetchGallery(parameters, options) {
|
|
|
89
53
|
} catch (error) {
|
|
90
54
|
return {
|
|
91
55
|
gallery: null,
|
|
92
|
-
...getErrorOutput(error, "Failed to fetch gallery")
|
|
56
|
+
...getErrorOutput(error, "Failed to fetch OCHRE gallery")
|
|
93
57
|
};
|
|
94
58
|
}
|
|
95
59
|
}
|
|
@@ -1,67 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { OCHRE_COLLECTION_CATEGORIES } from "../categories.mjs";
|
|
2
|
+
import { getErrorOutput } from "../errors.mjs";
|
|
3
|
+
import { requestOchre } from "./request.mjs";
|
|
4
|
+
import { uuidSchema } from "../schemas.mjs";
|
|
5
|
+
import { parseRequestedLanguages, resolveContentLanguages } from "../parsers/languages.mjs";
|
|
5
6
|
import { parseLinkedItems } from "../parsers/index.mjs";
|
|
6
7
|
import { XMLItemLinksData } from "../xml/schemas.mjs";
|
|
8
|
+
import { compileOchreQuery, stringLiteral } from "../xquery.mjs";
|
|
7
9
|
import * as v from "valibot";
|
|
8
|
-
import { XMLParser } from "fast-xml-parser";
|
|
9
10
|
//#region src/fetchers/item-children.ts
|
|
10
|
-
const ITEM_COLLECTION_CATEGORIES = [
|
|
11
|
-
"tree",
|
|
12
|
-
"bibliography",
|
|
13
|
-
"concept",
|
|
14
|
-
"spatialUnit",
|
|
15
|
-
"period",
|
|
16
|
-
"person",
|
|
17
|
-
"propertyVariable",
|
|
18
|
-
"propertyValue",
|
|
19
|
-
"resource",
|
|
20
|
-
"text",
|
|
21
|
-
"set"
|
|
22
|
-
];
|
|
23
|
-
function parseLanguages(languages) {
|
|
24
|
-
for (const language of languages) v.parse(iso639_3Schema, language);
|
|
25
|
-
return languages;
|
|
26
|
-
}
|
|
27
|
-
function isRecord(value) {
|
|
28
|
-
return typeof value === "object" && value !== null;
|
|
29
|
-
}
|
|
30
|
-
function collectContentLanguages(value, languages) {
|
|
31
|
-
if (Array.isArray(value)) {
|
|
32
|
-
for (const item of value) collectContentLanguages(item, languages);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
if (!isRecord(value)) return;
|
|
36
|
-
const content = value.content;
|
|
37
|
-
if (Array.isArray(content)) for (const contentItem of content) {
|
|
38
|
-
if (!isRecord(contentItem)) continue;
|
|
39
|
-
const language = contentItem.lang;
|
|
40
|
-
if (typeof language === "string" && language !== "zxx") languages.add(language);
|
|
41
|
-
}
|
|
42
|
-
for (const child of Object.values(value)) collectContentLanguages(child, languages);
|
|
43
|
-
}
|
|
44
|
-
function resolveItemChildrenLanguages(data, requestedLanguages) {
|
|
45
|
-
if (requestedLanguages.length > 0) return requestedLanguages;
|
|
46
|
-
const languages = /* @__PURE__ */ new Set();
|
|
47
|
-
collectContentLanguages(data.result.ochre.items, languages);
|
|
48
|
-
return languages.size > 0 ? [...languages] : [...DEFAULT_LANGUAGES];
|
|
49
|
-
}
|
|
50
11
|
function buildXQuery(uuid, category) {
|
|
51
|
-
const categories = category == null ?
|
|
12
|
+
const categories = category == null ? OCHRE_COLLECTION_CATEGORIES : typeof category === "string" ? [category] : category;
|
|
52
13
|
const collectionQueries = Array.from(categories, (possibleCategory) => `cts:search(fn:collection("ochre/${possibleCategory}")/ochre, $uuid-query)`);
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
${SUPPLEMENTAL_XQUERY_PROLOG}
|
|
56
|
-
|
|
57
|
-
declare function local:item-children($nodes as node()*) as node()* {
|
|
14
|
+
return compileOchreQuery({
|
|
15
|
+
declarations: [`declare function local:item-children($nodes as node()*) as node()* {
|
|
58
16
|
for $node in $nodes
|
|
59
17
|
return
|
|
60
18
|
if (local-name($node) = "heading")
|
|
61
19
|
then local:item-children($node/*)
|
|
62
20
|
else $node
|
|
63
|
-
}
|
|
64
|
-
|
|
21
|
+
};`],
|
|
22
|
+
body: ({ omitSupplemental }) => `
|
|
65
23
|
let $uuid := ${stringLiteral(uuid)}
|
|
66
24
|
let $uuid-query := cts:element-attribute-value-query(xs:QName("ochre"), xs:QName("uuid"), $uuid, "exact")
|
|
67
25
|
let $ochre := (
|
|
@@ -93,24 +51,20 @@ let $children :=
|
|
|
93
51
|
return
|
|
94
52
|
<ochre>
|
|
95
53
|
<items>{${omitSupplemental("$children")}}</items>
|
|
96
|
-
</ochre
|
|
54
|
+
</ochre>`
|
|
55
|
+
});
|
|
97
56
|
}
|
|
98
57
|
async function fetchItemChildren(uuid, options) {
|
|
99
58
|
try {
|
|
100
59
|
const parsedUuid = v.parse(uuidSchema, uuid);
|
|
101
|
-
const requestedLanguages = options?.languages
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
60
|
+
const requestedLanguages = parseRequestedLanguages(options?.languages);
|
|
61
|
+
const output = await requestOchre({
|
|
62
|
+
xquery: buildXQuery(parsedUuid, options?.category),
|
|
63
|
+
schema: XMLItemLinksData,
|
|
64
|
+
label: "OCHRE item children",
|
|
65
|
+
options
|
|
106
66
|
});
|
|
107
|
-
|
|
108
|
-
const dataRaw = await response.text();
|
|
109
|
-
const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
|
|
110
|
-
const { success, issues, output } = v.safeParse(XMLItemLinksData, data);
|
|
111
|
-
if (!success) throw createSchemaValidationError("Failed to parse OCHRE item children", issues);
|
|
112
|
-
restoreXMLMetadata(output, data);
|
|
113
|
-
const languages = resolveItemChildrenLanguages(output, requestedLanguages);
|
|
67
|
+
const languages = resolveContentLanguages(output.result.ochre.items, requestedLanguages);
|
|
114
68
|
return {
|
|
115
69
|
items: parseLinkedItems(output.result.ochre.items, {
|
|
116
70
|
containedItemCategory: options?.containedItemCategory,
|
|
@@ -1,39 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { ITEM_CATEGORY_ALIASES, OCHRE_COLLECTION_CATEGORIES } from "../categories.mjs";
|
|
2
|
+
import { getErrorOutput } from "../errors.mjs";
|
|
3
|
+
import { requestOchre } from "./request.mjs";
|
|
4
|
+
import { uuidSchema } from "../schemas.mjs";
|
|
5
|
+
import { parseRequestedLanguages, resolveContentLanguages } from "../parsers/languages.mjs";
|
|
5
6
|
import { parseLinkedItems } from "../parsers/index.mjs";
|
|
6
7
|
import { XMLItemLinksData } from "../xml/schemas.mjs";
|
|
8
|
+
import { compileOchreQuery, stringLiteral } from "../xquery.mjs";
|
|
7
9
|
import * as v from "valibot";
|
|
8
|
-
import { XMLParser } from "fast-xml-parser";
|
|
9
10
|
//#region src/fetchers/item-links.ts
|
|
10
|
-
function parseLanguages(languages) {
|
|
11
|
-
for (const language of languages) v.parse(iso639_3Schema, language);
|
|
12
|
-
return languages;
|
|
13
|
-
}
|
|
14
|
-
function isRecord(value) {
|
|
15
|
-
return typeof value === "object" && value !== null;
|
|
16
|
-
}
|
|
17
|
-
function collectContentLanguages(value, languages) {
|
|
18
|
-
if (Array.isArray(value)) {
|
|
19
|
-
for (const item of value) collectContentLanguages(item, languages);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
if (!isRecord(value)) return;
|
|
23
|
-
const content = value.content;
|
|
24
|
-
if (Array.isArray(content)) for (const contentItem of content) {
|
|
25
|
-
if (!isRecord(contentItem)) continue;
|
|
26
|
-
const language = contentItem.lang;
|
|
27
|
-
if (typeof language === "string" && language !== "zxx") languages.add(language);
|
|
28
|
-
}
|
|
29
|
-
for (const child of Object.values(value)) collectContentLanguages(child, languages);
|
|
30
|
-
}
|
|
31
|
-
function resolveItemLinksLanguages(data, requestedLanguages) {
|
|
32
|
-
if (requestedLanguages.length > 0) return requestedLanguages;
|
|
33
|
-
const languages = /* @__PURE__ */ new Set();
|
|
34
|
-
collectContentLanguages(data.result.ochre.items, languages);
|
|
35
|
-
return languages.size > 0 ? [...languages] : [...DEFAULT_LANGUAGES];
|
|
36
|
-
}
|
|
37
11
|
/**
|
|
38
12
|
* Build an XQuery string to fetch linked items from the OCHRE API.
|
|
39
13
|
*
|
|
@@ -41,20 +15,19 @@ function resolveItemLinksLanguages(data, requestedLanguages) {
|
|
|
41
15
|
* @returns An XQuery string
|
|
42
16
|
*/
|
|
43
17
|
function buildXQuery(uuid) {
|
|
44
|
-
const
|
|
18
|
+
const linkedItems = `for $link at $position in $link-nodes
|
|
19
|
+
let $uuid := $link/@uuid/string()
|
|
20
|
+
let $category := name($link)
|
|
21
|
+
where $uuid ne "" and not($uuid = $link-nodes[position() lt $position]/@uuid/string())
|
|
22
|
+
return
|
|
23
|
+
${OCHRE_COLLECTION_CATEGORIES.map((category) => {
|
|
24
|
+
return `if (${ITEM_CATEGORY_ALIASES[category].map((alias) => `$category = ${stringLiteral(alias)}`).join(" or ")}) then fn:collection("ochre/${category}")/ochre/${category}[@uuid = $uuid]`;
|
|
25
|
+
}).join("\n else ")}
|
|
26
|
+
else ()`;
|
|
27
|
+
return compileOchreQuery({ body: ({ omitSupplemental }) => `<ochre>{let $item-uuid := ${stringLiteral(uuid)}
|
|
45
28
|
|
|
46
29
|
let $source-items := (
|
|
47
|
-
fn:collection("ochre
|
|
48
|
-
fn:collection("ochre/bibliography")/ochre[@uuid = $item-uuid]/bibliography,
|
|
49
|
-
fn:collection("ochre/period")/ochre[@uuid = $item-uuid]/period,
|
|
50
|
-
fn:collection("ochre/person")/ochre[@uuid = $item-uuid]/person,
|
|
51
|
-
fn:collection("ochre/propertyVariable")/ochre[@uuid = $item-uuid]/propertyVariable,
|
|
52
|
-
fn:collection("ochre/propertyValue")/ochre[@uuid = $item-uuid]/propertyValue,
|
|
53
|
-
fn:collection("ochre/text")/ochre[@uuid = $item-uuid]/text,
|
|
54
|
-
fn:collection("ochre/tree")/ochre[@uuid = $item-uuid]/tree,
|
|
55
|
-
fn:collection("ochre/set")/ochre[@uuid = $item-uuid]/set,
|
|
56
|
-
fn:collection("ochre/spatialUnit")/ochre[@uuid = $item-uuid]/spatialUnit,
|
|
57
|
-
fn:collection("ochre/concept")/ochre[@uuid = $item-uuid]/concept
|
|
30
|
+
${OCHRE_COLLECTION_CATEGORIES.map((category) => ` fn:collection("ochre/${category}")/ochre[@uuid = $item-uuid]/${category}`).join(",\n")}
|
|
58
31
|
)
|
|
59
32
|
|
|
60
33
|
let $link-nodes := (
|
|
@@ -65,46 +38,20 @@ let $link-nodes := (
|
|
|
65
38
|
|
|
66
39
|
return
|
|
67
40
|
<items>{
|
|
68
|
-
${omitSupplemental(
|
|
69
|
-
|
|
70
|
-
let $category := name($link)
|
|
71
|
-
where $uuid ne "" and not($uuid = $link-nodes[position() lt $position]/@uuid/string())
|
|
72
|
-
return
|
|
73
|
-
if ($category = "resource") then fn:collection("ochre/resource")/ochre/resource[@uuid = $uuid]
|
|
74
|
-
else if ($category = "bibliography") then fn:collection("ochre/bibliography")/ochre/bibliography[@uuid = $uuid]
|
|
75
|
-
else if ($category = "period") then fn:collection("ochre/period")/ochre/period[@uuid = $uuid]
|
|
76
|
-
else if ($category = "person") then fn:collection("ochre/person")/ochre/person[@uuid = $uuid]
|
|
77
|
-
else if ($category = "propertyVariable" or $category = "variable") then fn:collection("ochre/propertyVariable")/ochre/propertyVariable[@uuid = $uuid]
|
|
78
|
-
else if ($category = "propertyValue" or $category = "value") then fn:collection("ochre/propertyValue")/ochre/propertyValue[@uuid = $uuid]
|
|
79
|
-
else if ($category = "text") then fn:collection("ochre/text")/ochre/text[@uuid = $uuid]
|
|
80
|
-
else if ($category = "tree") then fn:collection("ochre/tree")/ochre/tree[@uuid = $uuid]
|
|
81
|
-
else if ($category = "set") then fn:collection("ochre/set")/ochre/set[@uuid = $uuid]
|
|
82
|
-
else if ($category = "spatialUnit") then fn:collection("ochre/spatialUnit")/ochre/spatialUnit[@uuid = $uuid]
|
|
83
|
-
else if ($category = "concept") then fn:collection("ochre/concept")/ochre/concept[@uuid = $uuid]
|
|
84
|
-
else ()`)}
|
|
85
|
-
}</items>`;
|
|
86
|
-
return `xquery version "1.0-ml";
|
|
87
|
-
|
|
88
|
-
${SUPPLEMENTAL_XQUERY_PROLOG}
|
|
89
|
-
|
|
90
|
-
<ochre>{${xquery}}</ochre>`;
|
|
41
|
+
${omitSupplemental(linkedItems)}
|
|
42
|
+
}</items>}</ochre>` });
|
|
91
43
|
}
|
|
92
44
|
async function fetchItemLinks(uuid, options) {
|
|
93
45
|
try {
|
|
94
46
|
const parsedUuid = v.parse(uuidSchema, uuid);
|
|
95
|
-
const requestedLanguages = options?.languages
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
47
|
+
const requestedLanguages = parseRequestedLanguages(options?.languages);
|
|
48
|
+
const output = await requestOchre({
|
|
49
|
+
xquery: buildXQuery(parsedUuid),
|
|
50
|
+
schema: XMLItemLinksData,
|
|
51
|
+
label: "OCHRE item links",
|
|
52
|
+
options
|
|
100
53
|
});
|
|
101
|
-
|
|
102
|
-
const dataRaw = await response.text();
|
|
103
|
-
const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
|
|
104
|
-
const { success, issues, output } = v.safeParse(XMLItemLinksData, data);
|
|
105
|
-
if (!success) throw createSchemaValidationError("Failed to parse OCHRE item links", issues);
|
|
106
|
-
restoreXMLMetadata(output, data);
|
|
107
|
-
const languages = resolveItemLinksLanguages(output, requestedLanguages);
|
|
54
|
+
const languages = resolveContentLanguages(output.result.ochre.items, requestedLanguages);
|
|
108
55
|
return {
|
|
109
56
|
items: parseLinkedItems(output.result.ochre.items, {
|
|
110
57
|
containedItemCategory: options?.containedItemCategory,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { OcrString } from "../types/index.mjs";
|
|
2
|
+
import { OchreRequestOptions } from "./request.mjs";
|
|
2
3
|
//#region src/fetchers/item-ocr-data.d.ts
|
|
3
4
|
/**
|
|
4
5
|
* Fetches and parses the OCR strings of an OCHRE item that match a search value
|
|
@@ -23,10 +24,9 @@ import { OcrString } from "../types/index.mjs";
|
|
|
23
24
|
* @returns The matching OCR strings, an empty array when the item has no OCR
|
|
24
25
|
* layer or nothing matches, and a null output on fetch/parse errors
|
|
25
26
|
*/
|
|
26
|
-
export declare function fetchItemOcrData(uuid: string, value: string, options?: {
|
|
27
|
+
export declare function fetchItemOcrData(uuid: string, value: string, options?: OchreRequestOptions & {
|
|
27
28
|
matchMode?: "includes" | "exact";
|
|
28
29
|
isCaseSensitive?: boolean;
|
|
29
|
-
fetch?: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
|
|
30
30
|
}): Promise<{
|
|
31
31
|
ocrStrings: Array<OcrString>;
|
|
32
32
|
error: null;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { getErrorOutput } from "../errors.mjs";
|
|
2
|
+
import { requestOchre } from "./request.mjs";
|
|
3
3
|
import { itemOcrDataParametersSchema } from "../schemas.mjs";
|
|
4
|
+
import { stringLiteral } from "../xquery.mjs";
|
|
5
|
+
import { buildOcrWordPath } from "../ocr.mjs";
|
|
4
6
|
import * as v from "valibot";
|
|
5
|
-
import { XMLParser } from "fast-xml-parser";
|
|
6
7
|
//#region src/fetchers/item-ocr-data.ts
|
|
7
8
|
const OCR_STRING_VERTEX_REGEX = /\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/g;
|
|
8
9
|
/**
|
|
@@ -83,7 +84,7 @@ declare variable $terms := (${termValues.join(", ")});
|
|
|
83
84
|
|
|
84
85
|
let $ochre := doc(${stringLiteral(uuid)})/ochre
|
|
85
86
|
let $ocrStrings :=
|
|
86
|
-
for $string in $
|
|
87
|
+
for $string in ${buildOcrWordPath("$ochre")}
|
|
87
88
|
where (some $term in $terms satisfies ${matchExpression})
|
|
88
89
|
return <ocrString
|
|
89
90
|
resourceUuid="{string($string/ancestor::*[local-name(.) = "resource"][1]/@uuid)}"
|
|
@@ -137,17 +138,12 @@ async function fetchItemOcrData(uuid, value, options) {
|
|
|
137
138
|
matchMode: parameters.matchMode,
|
|
138
139
|
isCaseSensitive: parameters.isCaseSensitive
|
|
139
140
|
});
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const dataRaw = await response.text();
|
|
147
|
-
const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
|
|
148
|
-
const { success, issues, output } = v.safeParse(responseSchema, data);
|
|
149
|
-
if (!success) throw createSchemaValidationError("Failed to parse OCHRE item OCR data", issues);
|
|
150
|
-
const { found, ocrString } = output.result.ochre.ocrStrings;
|
|
141
|
+
const { found, ocrString } = (await requestOchre({
|
|
142
|
+
xquery,
|
|
143
|
+
schema: responseSchema,
|
|
144
|
+
label: "OCHRE item OCR data",
|
|
145
|
+
options
|
|
146
|
+
})).result.ochre.ocrStrings;
|
|
151
147
|
if (found !== "true") throw new Error(`No OCHRE item found for UUID: ${parameters.uuid}`, { cause: parameters.uuid });
|
|
152
148
|
const parsedOcrStrings = ocrString == null ? [] : Array.isArray(ocrString) ? ocrString : [ocrString];
|
|
153
149
|
return {
|
package/dist/fetchers/item.d.mts
CHANGED
|
@@ -10,21 +10,6 @@ type FetchItemResult<TItem> = Promise<{
|
|
|
10
10
|
error: string;
|
|
11
11
|
detailedError: string;
|
|
12
12
|
}>;
|
|
13
|
-
/**
|
|
14
|
-
* Defines a reusable languages tuple with validation and literal type inference.
|
|
15
|
-
*
|
|
16
|
-
* Inline arrays can be passed directly to fetchItem:
|
|
17
|
-
* `fetchItem(uuid, { languages: ["eng", "spa"] })`.
|
|
18
|
-
*
|
|
19
|
-
* Use this helper when the language set is stored separately:
|
|
20
|
-
* `const languages = defineLanguages("eng", "spa")`.
|
|
21
|
-
*/
|
|
22
|
-
export declare function defineLanguages<const TLanguages extends ReadonlyArray<string>>(...languages: TLanguages): TLanguages;
|
|
23
|
-
/**
|
|
24
|
-
* @deprecated Pass inline language arrays directly to fetchItem, or use
|
|
25
|
-
* defineLanguages("eng", "spa") for reusable language tuples.
|
|
26
|
-
*/
|
|
27
|
-
export declare function withLanguages<const TLanguages extends ReadonlyArray<string>>(languages: TLanguages): TLanguages;
|
|
28
13
|
/**
|
|
29
14
|
* Fetches an OCHRE item by UUID from the OCHRE API
|
|
30
15
|
*
|