ochre-sdk 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- 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 -1966
- package/dist/index.mjs +9 -7201
- 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
package/README.md
CHANGED
|
@@ -47,7 +47,9 @@ present and `error` is `null`; on failure, the parsed value is `null` and
|
|
|
47
47
|
|
|
48
48
|
- `fetchItem(uuid, options)` fetches and parses a single OCHRE item. Passing
|
|
49
49
|
`category` narrows the returned TypeScript type, and `containedItemCategory`
|
|
50
|
-
controls how nested Tree or Set contents are parsed.
|
|
50
|
+
controls how nested Tree or Set contents are parsed. For large Trees or Sets,
|
|
51
|
+
pass `shouldOmitEmbeddedItems: true` to fetch the top-level item without its
|
|
52
|
+
embedded `<items>` payload.
|
|
51
53
|
- `fetchItemLinks(uuid, options)` fetches items linked from a source item and
|
|
52
54
|
parses them as embedded OCHRE items.
|
|
53
55
|
- `fetchGallery(params, options)` fetches paginated resource galleries with an
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { X2jOptions } from "fast-xml-parser";
|
|
2
|
+
|
|
3
|
+
//#region src/constants.d.ts
|
|
4
|
+
declare const XML_ARRAY_TAGS: ReadonlyArray<string>;
|
|
5
|
+
declare const XML_PARSER_OPTIONS: X2jOptions;
|
|
6
|
+
declare const DEFAULT_LANGUAGES: readonly ["eng"];
|
|
7
|
+
declare const BELONGS_TO_COLLECTION_UUID = "30054cb2-909a-4f34-8db9-8fe7369d691d";
|
|
8
|
+
declare const PRESENTATION_ITEM_UUID = "f1c131b6-1498-48a4-95bf-a9edae9fd518";
|
|
9
|
+
declare const TEXT_ANNOTATION_UUID = "b9ca2732-78f4-416e-b77f-dae7647e68a9";
|
|
10
|
+
declare const TEXT_ANNOTATION_HOVER_CARD_UUID = "c7f6a08a-f07b-49b6-bcb1-af485da3c58f";
|
|
11
|
+
declare const TEXT_ANNOTATION_ITEM_PAGE_VARIANT_UUID = "bf4476ab-6bc8-40d0-a001-1446213c72ce";
|
|
12
|
+
declare const TEXT_ANNOTATION_ENTRY_PAGE_VARIANT_UUID = "9d52db95-a9cf-45f7-a0bf-fc9ba9f0aae0";
|
|
13
|
+
declare const TEXT_ANNOTATION_TEXT_STYLING_UUID = "3e6f86ab-df81-45ae-8257-e2867357df56";
|
|
14
|
+
declare const TEXT_ANNOTATION_TEXT_STYLING_VARIANT_UUID = "e1647bef-d801-4100-bdde-d081c422f763";
|
|
15
|
+
declare const TEXT_ANNOTATION_TEXT_STYLING_HEADING_LEVEL_UUID = "d4266f0b-3f8d-4b32-8c15-4b229c8bb11e";
|
|
16
|
+
//#endregion
|
|
17
|
+
export { BELONGS_TO_COLLECTION_UUID, DEFAULT_LANGUAGES, PRESENTATION_ITEM_UUID, TEXT_ANNOTATION_ENTRY_PAGE_VARIANT_UUID, TEXT_ANNOTATION_HOVER_CARD_UUID, TEXT_ANNOTATION_ITEM_PAGE_VARIANT_UUID, TEXT_ANNOTATION_TEXT_STYLING_HEADING_LEVEL_UUID, TEXT_ANNOTATION_TEXT_STYLING_UUID, TEXT_ANNOTATION_TEXT_STYLING_VARIANT_UUID, TEXT_ANNOTATION_UUID, XML_ARRAY_TAGS, XML_PARSER_OPTIONS };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//#region src/constants.ts
|
|
2
|
+
const XML_ARRAY_TAGS = [
|
|
3
|
+
"string",
|
|
4
|
+
"content",
|
|
5
|
+
"tree",
|
|
6
|
+
"bibliography",
|
|
7
|
+
"spatialUnit",
|
|
8
|
+
"concept",
|
|
9
|
+
"person",
|
|
10
|
+
"period",
|
|
11
|
+
"propertyValue",
|
|
12
|
+
"propertyVariable",
|
|
13
|
+
"resource",
|
|
14
|
+
"text",
|
|
15
|
+
"set",
|
|
16
|
+
"dictionaryUnit",
|
|
17
|
+
"variable",
|
|
18
|
+
"property",
|
|
19
|
+
"value",
|
|
20
|
+
"context",
|
|
21
|
+
"creator",
|
|
22
|
+
"author",
|
|
23
|
+
"event",
|
|
24
|
+
"interpretation",
|
|
25
|
+
"observation",
|
|
26
|
+
"observer",
|
|
27
|
+
"heading",
|
|
28
|
+
"note",
|
|
29
|
+
"reference",
|
|
30
|
+
"coord",
|
|
31
|
+
"area",
|
|
32
|
+
"footnote",
|
|
33
|
+
"language",
|
|
34
|
+
"section",
|
|
35
|
+
"translation",
|
|
36
|
+
"phonemic",
|
|
37
|
+
"editor",
|
|
38
|
+
"publisher",
|
|
39
|
+
"scope",
|
|
40
|
+
"level",
|
|
41
|
+
"style",
|
|
42
|
+
"flattenContexts",
|
|
43
|
+
"suppressContexts",
|
|
44
|
+
"filterContexts",
|
|
45
|
+
"sortContexts",
|
|
46
|
+
"detailContexts",
|
|
47
|
+
"downloadContexts",
|
|
48
|
+
"labelContexts",
|
|
49
|
+
"prominentContexts"
|
|
50
|
+
];
|
|
51
|
+
const XML_PARSER_OPTIONS = {
|
|
52
|
+
alwaysCreateTextNode: true,
|
|
53
|
+
captureMetaData: true,
|
|
54
|
+
ignoreAttributes: false,
|
|
55
|
+
removeNSPrefix: true,
|
|
56
|
+
ignorePiTags: true,
|
|
57
|
+
trimValues: false,
|
|
58
|
+
parseTagValue: false,
|
|
59
|
+
parseAttributeValue: false,
|
|
60
|
+
attributeNamePrefix: "",
|
|
61
|
+
textNodeName: "payload",
|
|
62
|
+
stopNodes: ["*.referenceFormatDiv", "*.citationFormatSpan"],
|
|
63
|
+
htmlEntities: true,
|
|
64
|
+
isArray(tagName, jPath, isLeafNode, isAttribute) {
|
|
65
|
+
if (isAttribute) return false;
|
|
66
|
+
if (XML_ARRAY_TAGS.includes(tagName)) return true;
|
|
67
|
+
return false;
|
|
68
|
+
},
|
|
69
|
+
attributeValueProcessor: (attrName, attrValue) => {
|
|
70
|
+
if (attrValue.startsWith("xs:")) return attrValue.replace("xs:", "");
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const DEFAULT_LANGUAGES = ["eng"];
|
|
75
|
+
const BELONGS_TO_COLLECTION_UUID = "30054cb2-909a-4f34-8db9-8fe7369d691d";
|
|
76
|
+
const PRESENTATION_ITEM_UUID = "f1c131b6-1498-48a4-95bf-a9edae9fd518";
|
|
77
|
+
const TEXT_ANNOTATION_UUID = "b9ca2732-78f4-416e-b77f-dae7647e68a9";
|
|
78
|
+
const TEXT_ANNOTATION_HOVER_CARD_UUID = "c7f6a08a-f07b-49b6-bcb1-af485da3c58f";
|
|
79
|
+
const TEXT_ANNOTATION_ITEM_PAGE_VARIANT_UUID = "bf4476ab-6bc8-40d0-a001-1446213c72ce";
|
|
80
|
+
const TEXT_ANNOTATION_ENTRY_PAGE_VARIANT_UUID = "9d52db95-a9cf-45f7-a0bf-fc9ba9f0aae0";
|
|
81
|
+
const TEXT_ANNOTATION_TEXT_STYLING_UUID = "3e6f86ab-df81-45ae-8257-e2867357df56";
|
|
82
|
+
const TEXT_ANNOTATION_TEXT_STYLING_VARIANT_UUID = "e1647bef-d801-4100-bdde-d081c422f763";
|
|
83
|
+
const TEXT_ANNOTATION_TEXT_STYLING_HEADING_LEVEL_UUID = "d4266f0b-3f8d-4b32-8c15-4b229c8bb11e";
|
|
84
|
+
//#endregion
|
|
85
|
+
export { BELONGS_TO_COLLECTION_UUID, DEFAULT_LANGUAGES, PRESENTATION_ITEM_UUID, TEXT_ANNOTATION_ENTRY_PAGE_VARIANT_UUID, TEXT_ANNOTATION_HOVER_CARD_UUID, TEXT_ANNOTATION_ITEM_PAGE_VARIANT_UUID, TEXT_ANNOTATION_TEXT_STYLING_HEADING_LEVEL_UUID, TEXT_ANNOTATION_TEXT_STYLING_UUID, TEXT_ANNOTATION_TEXT_STYLING_VARIANT_UUID, TEXT_ANNOTATION_UUID, XML_ARRAY_TAGS, XML_PARSER_OPTIONS };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Gallery } from "../types/index.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/fetchers/gallery.d.ts
|
|
4
|
+
type FetchFunction = (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
|
|
5
|
+
type FetchGalleryBaseOptions<TLanguages extends ReadonlyArray<string> | undefined = undefined> = {
|
|
6
|
+
languages?: TLanguages;
|
|
7
|
+
fetch?: FetchFunction;
|
|
8
|
+
};
|
|
9
|
+
type FetchGalleryLanguages<TLanguages extends ReadonlyArray<string> | undefined> = TLanguages extends readonly [] ? ReadonlyArray<string> : TLanguages extends ReadonlyArray<string> ? TLanguages : ReadonlyArray<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Fetches and parses a gallery from the OCHRE API
|
|
12
|
+
*
|
|
13
|
+
* @param params - The parameters for the fetch
|
|
14
|
+
* @param params.uuid - The UUID of the gallery
|
|
15
|
+
* @param params.filter - The filter to apply to the gallery
|
|
16
|
+
* @param params.page - The page number to fetch
|
|
17
|
+
* @param params.perPage - The number of items per page
|
|
18
|
+
* @param options - The options for the fetch
|
|
19
|
+
* @param options.languages - Language codes to parse. Inline arrays preserve literal types automatically.
|
|
20
|
+
* @param options.fetch - The fetch function to use
|
|
21
|
+
* @returns The parsed gallery or an error message if the fetch/parse fails
|
|
22
|
+
*/
|
|
23
|
+
declare function fetchGallery<const TLanguages extends ReadonlyArray<string> | undefined = undefined>(params: {
|
|
24
|
+
uuid: string;
|
|
25
|
+
filter?: string;
|
|
26
|
+
page: number;
|
|
27
|
+
perPage: number;
|
|
28
|
+
}, options?: FetchGalleryBaseOptions<TLanguages>): Promise<{
|
|
29
|
+
gallery: Gallery<FetchGalleryLanguages<TLanguages>>;
|
|
30
|
+
error: null;
|
|
31
|
+
detailedError: null;
|
|
32
|
+
} | {
|
|
33
|
+
gallery: null;
|
|
34
|
+
error: string;
|
|
35
|
+
detailedError: string;
|
|
36
|
+
}>;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { fetchGallery };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { DEFAULT_LANGUAGES, XML_PARSER_OPTIONS } from "../constants.mjs";
|
|
2
|
+
import { createSchemaValidationError, getErrorOutput, stringLiteral } from "../utils.mjs";
|
|
3
|
+
import { gallerySchema, iso639_3Schema } from "../schemas.mjs";
|
|
4
|
+
import { restoreXMLMetadata } from "../xml/metadata.mjs";
|
|
5
|
+
import { parseGallery } from "../parsers/index.mjs";
|
|
6
|
+
import { XMLGalleryData } from "../xml/schemas.mjs";
|
|
7
|
+
import * as v from "valibot";
|
|
8
|
+
import { XMLParser } from "fast-xml-parser";
|
|
9
|
+
//#region src/fetchers/gallery.ts
|
|
10
|
+
function parseLanguages(languages) {
|
|
11
|
+
const parsedLanguages = [];
|
|
12
|
+
for (const language of languages) parsedLanguages.push(v.parse(iso639_3Schema, language));
|
|
13
|
+
return parsedLanguages;
|
|
14
|
+
}
|
|
15
|
+
function isRecord(value) {
|
|
16
|
+
return typeof value === "object" && value !== null;
|
|
17
|
+
}
|
|
18
|
+
function collectContentLanguages(value, languages) {
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
for (const item of value) collectContentLanguages(item, languages);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!isRecord(value)) return;
|
|
24
|
+
const content = value.content;
|
|
25
|
+
if (Array.isArray(content)) for (const contentItem of content) {
|
|
26
|
+
if (!isRecord(contentItem)) continue;
|
|
27
|
+
const language = contentItem.lang;
|
|
28
|
+
if (typeof language === "string" && language !== "zxx") languages.add(language);
|
|
29
|
+
}
|
|
30
|
+
for (const child of Object.values(value)) collectContentLanguages(child, languages);
|
|
31
|
+
}
|
|
32
|
+
function resolveGalleryLanguages(data, requestedLanguages) {
|
|
33
|
+
if (requestedLanguages.length > 0) return requestedLanguages;
|
|
34
|
+
const languages = /* @__PURE__ */ new Set();
|
|
35
|
+
collectContentLanguages(data.result.ochre.gallery, languages);
|
|
36
|
+
return languages.size > 0 ? [...languages] : [...DEFAULT_LANGUAGES];
|
|
37
|
+
}
|
|
38
|
+
function buildXQuery(params) {
|
|
39
|
+
const { uuid, filter, page, perPage } = params;
|
|
40
|
+
const start = (page - 1) * perPage + 1;
|
|
41
|
+
const filterLiteral = stringLiteral(filter?.trim() ?? "");
|
|
42
|
+
return `<ochre>{
|
|
43
|
+
for $q in doc()/ochre[@uuid=${stringLiteral(uuid)}]
|
|
44
|
+
let $filter := ${filterLiteral}
|
|
45
|
+
let $resources := $q//items/resource
|
|
46
|
+
let $filtered :=
|
|
47
|
+
if ($filter = "")
|
|
48
|
+
then $resources
|
|
49
|
+
else $resources[contains(lower-case(string-join(identification/label//text(), "")), lower-case($filter))]
|
|
50
|
+
let $maxLength := count($filtered)
|
|
51
|
+
return <gallery maxLength="{$maxLength}">{
|
|
52
|
+
$q/metadata/project,
|
|
53
|
+
$q/metadata/item,
|
|
54
|
+
subsequence($filtered, ${start}, ${perPage})
|
|
55
|
+
}</gallery>
|
|
56
|
+
}</ochre>`;
|
|
57
|
+
}
|
|
58
|
+
async function fetchGallery(params, options) {
|
|
59
|
+
try {
|
|
60
|
+
const { uuid, filter, page, perPage } = v.parse(gallerySchema, params);
|
|
61
|
+
const requestedLanguages = options?.languages == null ? [] : parseLanguages(options.languages);
|
|
62
|
+
const response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
|
|
63
|
+
method: "POST",
|
|
64
|
+
body: buildXQuery({
|
|
65
|
+
uuid,
|
|
66
|
+
filter,
|
|
67
|
+
page,
|
|
68
|
+
perPage
|
|
69
|
+
}),
|
|
70
|
+
headers: { "Content-Type": "application/xquery" }
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok) throw new Error("Error fetching gallery items, please try again later.", { cause: response.statusText });
|
|
73
|
+
const dataRaw = await response.text();
|
|
74
|
+
const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
|
|
75
|
+
const { success, issues, output } = v.safeParse(XMLGalleryData, data);
|
|
76
|
+
if (!success) throw createSchemaValidationError("Failed to parse gallery XML", issues);
|
|
77
|
+
restoreXMLMetadata(output, data);
|
|
78
|
+
return {
|
|
79
|
+
gallery: parseGallery(output, { languages: resolveGalleryLanguages(output, requestedLanguages) }),
|
|
80
|
+
error: null,
|
|
81
|
+
detailedError: null
|
|
82
|
+
};
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
gallery: null,
|
|
86
|
+
...getErrorOutput(error, "Failed to fetch gallery")
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
export { fetchGallery };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ContainedItemCategoryFromOption, ContainedItemCategoryOption, Item, ItemCategory, ItemContainerCategory } from "../types/index.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/fetchers/item-links.d.ts
|
|
4
|
+
type FetchFunction = (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
|
|
5
|
+
type FetchItemLinksBaseOptions<TLanguages extends ReadonlyArray<string> | undefined = undefined> = {
|
|
6
|
+
languages?: TLanguages;
|
|
7
|
+
fetch?: FetchFunction;
|
|
8
|
+
};
|
|
9
|
+
type FetchItemLinksLanguages<TLanguages extends ReadonlyArray<string> | undefined> = TLanguages extends readonly [] ? ReadonlyArray<string> : TLanguages extends ReadonlyArray<string> ? TLanguages : ReadonlyArray<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Fetches linked OCHRE items by source-item UUID.
|
|
12
|
+
*
|
|
13
|
+
* @param uuid - The UUID of the OCHRE item whose linked items should be fetched
|
|
14
|
+
* @param options - Fetch and parser options
|
|
15
|
+
* @param options.containedItemCategory - The category of items inside linked Trees/Sets to parse. Tree accepts one category; Set accepts one category or an array.
|
|
16
|
+
* @param options.languages - Language codes to parse. Inline arrays preserve literal types automatically.
|
|
17
|
+
* @param options.fetch - Custom fetch function to use instead of the default fetch
|
|
18
|
+
* @returns An object containing parsed linked items
|
|
19
|
+
*/
|
|
20
|
+
declare function fetchItemLinks<const TContainedItemCategory extends ContainedItemCategoryOption<ItemContainerCategory> | undefined = undefined, const TLanguages extends ReadonlyArray<string> | undefined = undefined>(uuid: string, options?: FetchItemLinksBaseOptions<TLanguages> & {
|
|
21
|
+
containedItemCategory?: TContainedItemCategory;
|
|
22
|
+
}): Promise<{
|
|
23
|
+
items: Array<Item<ItemCategory, ContainedItemCategoryFromOption<ItemCategory, TContainedItemCategory>, FetchItemLinksLanguages<TLanguages>, "embedded">>;
|
|
24
|
+
error: null;
|
|
25
|
+
detailedError: null;
|
|
26
|
+
} | {
|
|
27
|
+
items: null;
|
|
28
|
+
error: string;
|
|
29
|
+
detailedError: string;
|
|
30
|
+
}>;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { fetchItemLinks };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { DEFAULT_LANGUAGES, XML_PARSER_OPTIONS } from "../constants.mjs";
|
|
2
|
+
import { createSchemaValidationError, getErrorOutput } from "../utils.mjs";
|
|
3
|
+
import { iso639_3Schema, uuidSchema } from "../schemas.mjs";
|
|
4
|
+
import { restoreXMLMetadata } from "../xml/metadata.mjs";
|
|
5
|
+
import { parseLinkedItems } from "../parsers/index.mjs";
|
|
6
|
+
import { XMLItemLinksData } from "../xml/schemas.mjs";
|
|
7
|
+
import * as v from "valibot";
|
|
8
|
+
import { XMLParser } from "fast-xml-parser";
|
|
9
|
+
//#region src/fetchers/item-links.ts
|
|
10
|
+
function parseLanguages(languages) {
|
|
11
|
+
const parsedLanguages = [];
|
|
12
|
+
for (const language of languages) parsedLanguages.push(v.parse(iso639_3Schema, language));
|
|
13
|
+
return parsedLanguages;
|
|
14
|
+
}
|
|
15
|
+
function isRecord(value) {
|
|
16
|
+
return typeof value === "object" && value !== null;
|
|
17
|
+
}
|
|
18
|
+
function collectContentLanguages(value, languages) {
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
for (const item of value) collectContentLanguages(item, languages);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!isRecord(value)) return;
|
|
24
|
+
const content = value.content;
|
|
25
|
+
if (Array.isArray(content)) for (const contentItem of content) {
|
|
26
|
+
if (!isRecord(contentItem)) continue;
|
|
27
|
+
const language = contentItem.lang;
|
|
28
|
+
if (typeof language === "string" && language !== "zxx") languages.add(language);
|
|
29
|
+
}
|
|
30
|
+
for (const child of Object.values(value)) collectContentLanguages(child, languages);
|
|
31
|
+
}
|
|
32
|
+
function resolveItemLinksLanguages(data, requestedLanguages) {
|
|
33
|
+
if (requestedLanguages.length > 0) return requestedLanguages;
|
|
34
|
+
const languages = /* @__PURE__ */ new Set();
|
|
35
|
+
collectContentLanguages(data.result.ochre.items, languages);
|
|
36
|
+
return languages.size > 0 ? [...languages] : [...DEFAULT_LANGUAGES];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build an XQuery string to fetch linked items from the OCHRE API.
|
|
40
|
+
*
|
|
41
|
+
* @param uuid - The UUID of the OCHRE item whose links should be fetched
|
|
42
|
+
* @returns An XQuery string
|
|
43
|
+
*/
|
|
44
|
+
function buildXQuery(uuid) {
|
|
45
|
+
return `<ochre>{${`let $item-uuid := "${uuid}"
|
|
46
|
+
|
|
47
|
+
let $source-items := (
|
|
48
|
+
fn:collection("ochre/resource")/ochre[@uuid = $item-uuid]/resource,
|
|
49
|
+
fn:collection("ochre/bibliography")/ochre[@uuid = $item-uuid]/bibliography,
|
|
50
|
+
fn:collection("ochre/period")/ochre[@uuid = $item-uuid]/period,
|
|
51
|
+
fn:collection("ochre/person")/ochre[@uuid = $item-uuid]/person,
|
|
52
|
+
fn:collection("ochre/propertyVariable")/ochre[@uuid = $item-uuid]/propertyVariable,
|
|
53
|
+
fn:collection("ochre/propertyValue")/ochre[@uuid = $item-uuid]/propertyValue,
|
|
54
|
+
fn:collection("ochre/text")/ochre[@uuid = $item-uuid]/text,
|
|
55
|
+
fn:collection("ochre/tree")/ochre[@uuid = $item-uuid]/tree,
|
|
56
|
+
fn:collection("ochre/set")/ochre[@uuid = $item-uuid]/set,
|
|
57
|
+
fn:collection("ochre/spatialUnit")/ochre[@uuid = $item-uuid]/spatialUnit,
|
|
58
|
+
fn:collection("ochre/concept")/ochre[@uuid = $item-uuid]/concept
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
let $link-nodes := (
|
|
62
|
+
$source-items/links/*,
|
|
63
|
+
$source-items/observations/observation/links/*,
|
|
64
|
+
$source-items/interpretations/interpretation/links/*
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return
|
|
68
|
+
<items>{
|
|
69
|
+
for $link at $position in $link-nodes
|
|
70
|
+
let $uuid := $link/@uuid/string()
|
|
71
|
+
let $category := name($link)
|
|
72
|
+
where $uuid ne "" and not($uuid = $link-nodes[position() lt $position]/@uuid/string())
|
|
73
|
+
return
|
|
74
|
+
if ($category = "resource") then fn:collection("ochre/resource")/ochre/resource[@uuid = $uuid]
|
|
75
|
+
else if ($category = "bibliography") then fn:collection("ochre/bibliography")/ochre/bibliography[@uuid = $uuid]
|
|
76
|
+
else if ($category = "period") then fn:collection("ochre/period")/ochre/period[@uuid = $uuid]
|
|
77
|
+
else if ($category = "person") then fn:collection("ochre/person")/ochre/person[@uuid = $uuid]
|
|
78
|
+
else if ($category = "propertyVariable" or $category = "variable") then fn:collection("ochre/propertyVariable")/ochre/propertyVariable[@uuid = $uuid]
|
|
79
|
+
else if ($category = "propertyValue" or $category = "value") then fn:collection("ochre/propertyValue")/ochre/propertyValue[@uuid = $uuid]
|
|
80
|
+
else if ($category = "text") then fn:collection("ochre/text")/ochre/text[@uuid = $uuid]
|
|
81
|
+
else if ($category = "tree") then fn:collection("ochre/tree")/ochre/tree[@uuid = $uuid]
|
|
82
|
+
else if ($category = "set") then fn:collection("ochre/set")/ochre/set[@uuid = $uuid]
|
|
83
|
+
else if ($category = "spatialUnit") then fn:collection("ochre/spatialUnit")/ochre/spatialUnit[@uuid = $uuid]
|
|
84
|
+
else if ($category = "concept") then fn:collection("ochre/concept")/ochre/concept[@uuid = $uuid]
|
|
85
|
+
else ()
|
|
86
|
+
}</items>`}}</ochre>`;
|
|
87
|
+
}
|
|
88
|
+
async function fetchItemLinks(uuid, options) {
|
|
89
|
+
try {
|
|
90
|
+
const parsedUuid = v.parse(uuidSchema, uuid);
|
|
91
|
+
const requestedLanguages = options?.languages == null ? [] : parseLanguages(options.languages);
|
|
92
|
+
const response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
|
|
93
|
+
method: "POST",
|
|
94
|
+
body: buildXQuery(parsedUuid),
|
|
95
|
+
headers: { "Content-Type": "application/xquery" }
|
|
96
|
+
});
|
|
97
|
+
if (!response.ok) throw new Error("Failed to fetch OCHRE item links", { cause: response.statusText });
|
|
98
|
+
const dataRaw = await response.text();
|
|
99
|
+
const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
|
|
100
|
+
const { success, issues, output } = v.safeParse(XMLItemLinksData, data);
|
|
101
|
+
if (!success) throw createSchemaValidationError("Failed to parse OCHRE item links", issues);
|
|
102
|
+
restoreXMLMetadata(output, data);
|
|
103
|
+
const languages = resolveItemLinksLanguages(output, requestedLanguages);
|
|
104
|
+
return {
|
|
105
|
+
items: parseLinkedItems(output.result.ochre.items, {
|
|
106
|
+
containedItemCategory: options?.containedItemCategory,
|
|
107
|
+
languages
|
|
108
|
+
}),
|
|
109
|
+
error: null,
|
|
110
|
+
detailedError: null
|
|
111
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return {
|
|
114
|
+
items: null,
|
|
115
|
+
...getErrorOutput(error, "Unknown error")
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
export { fetchItemLinks };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ContainedItemCategory, ContainedItemCategoryFromOption, ContainedItemCategoryOption, Item, ItemCategory, ItemContainerCategory, ItemWithoutEmbeddedItems } from "../types/index.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/fetchers/item.d.ts
|
|
4
|
+
type FetchFunction = (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
|
|
5
|
+
type FetchItemBaseOptions<TLanguages extends ReadonlyArray<string> | undefined = undefined> = {
|
|
6
|
+
languages?: TLanguages;
|
|
7
|
+
fetch?: FetchFunction;
|
|
8
|
+
};
|
|
9
|
+
type FetchItemNoOmitEmbeddedItemsOption = {
|
|
10
|
+
shouldOmitEmbeddedItems?: false;
|
|
11
|
+
};
|
|
12
|
+
type FetchItemLanguages<TLanguages extends ReadonlyArray<string> | undefined> = TLanguages extends readonly [] ? ReadonlyArray<string> : TLanguages extends ReadonlyArray<string> ? TLanguages : ReadonlyArray<string>;
|
|
13
|
+
type FetchItemSuccess<TItem> = {
|
|
14
|
+
item: TItem;
|
|
15
|
+
error: null;
|
|
16
|
+
detailedError: null;
|
|
17
|
+
};
|
|
18
|
+
type FetchItemError = {
|
|
19
|
+
item: null;
|
|
20
|
+
error: string;
|
|
21
|
+
detailedError: string;
|
|
22
|
+
};
|
|
23
|
+
type FetchItemResult<TItem> = Promise<FetchItemSuccess<TItem> | FetchItemError>;
|
|
24
|
+
/**
|
|
25
|
+
* Defines a reusable languages tuple with validation and literal type inference.
|
|
26
|
+
*
|
|
27
|
+
* Inline arrays can be passed directly to fetchItem:
|
|
28
|
+
* `fetchItem(uuid, { languages: ["eng", "spa"] })`.
|
|
29
|
+
*
|
|
30
|
+
* Use this helper when the language set is stored separately:
|
|
31
|
+
* `const languages = defineLanguages("eng", "spa")`.
|
|
32
|
+
*/
|
|
33
|
+
declare function defineLanguages<const TLanguages extends ReadonlyArray<string>>(...languages: TLanguages): TLanguages;
|
|
34
|
+
/**
|
|
35
|
+
* @deprecated Pass inline language arrays directly to fetchItem, or use
|
|
36
|
+
* defineLanguages("eng", "spa") for reusable language tuples.
|
|
37
|
+
*/
|
|
38
|
+
declare function withLanguages<const TLanguages extends ReadonlyArray<string>>(languages: TLanguages): TLanguages;
|
|
39
|
+
/**
|
|
40
|
+
* Fetches an OCHRE item by UUID from the OCHRE API
|
|
41
|
+
*
|
|
42
|
+
* @param uuid - The UUID of the OCHRE item to fetch
|
|
43
|
+
* @param options - Required options object
|
|
44
|
+
* @param options.category - The category of the OCHRE item to fetch
|
|
45
|
+
* @param options.containedItemCategory - The category of items inside the OCHRE item to fetch. Only valid for Trees and Sets. Tree accepts one category; Set accepts one category or an array.
|
|
46
|
+
* @param options.shouldOmitEmbeddedItems - Whether to omit the embedded `<items>` node when fetching a Tree or Set.
|
|
47
|
+
* @param options.languages - Language codes to parse. Inline arrays preserve literal types automatically.
|
|
48
|
+
* @param options.fetch - Custom fetch function to use instead of the default fetch
|
|
49
|
+
* @returns An object containing the parsed item
|
|
50
|
+
*/
|
|
51
|
+
declare function fetchItem<const TContainedItemCategory extends ContainedItemCategoryOption<ItemContainerCategory> | undefined = undefined, const TLanguages extends ReadonlyArray<string> | undefined = undefined>(uuid: string, options?: FetchItemBaseOptions<TLanguages> & FetchItemNoOmitEmbeddedItemsOption & {
|
|
52
|
+
category?: undefined;
|
|
53
|
+
containedItemCategory?: TContainedItemCategory;
|
|
54
|
+
}): FetchItemResult<Item<ItemCategory, ContainedItemCategoryFromOption<ItemCategory, TContainedItemCategory>, FetchItemLanguages<TLanguages>>>;
|
|
55
|
+
declare function fetchItem<const TContainedItemCategory extends ContainedItemCategoryOption<ItemContainerCategory> | undefined = undefined, const TLanguages extends ReadonlyArray<string> | undefined = undefined>(uuid: string, options: FetchItemBaseOptions<TLanguages> & {
|
|
56
|
+
category?: undefined;
|
|
57
|
+
containedItemCategory?: TContainedItemCategory;
|
|
58
|
+
shouldOmitEmbeddedItems: true;
|
|
59
|
+
}): FetchItemResult<ItemWithoutEmbeddedItems<ItemContainerCategory, ContainedItemCategoryFromOption<ItemContainerCategory, TContainedItemCategory>, FetchItemLanguages<TLanguages>>>;
|
|
60
|
+
declare function fetchItem<const TCategory extends ItemContainerCategory, const TContainedItemCategory extends ContainedItemCategoryOption<TCategory> | undefined = undefined, const TLanguages extends ReadonlyArray<string> | undefined = undefined>(uuid: string, options: FetchItemBaseOptions<TLanguages> & FetchItemNoOmitEmbeddedItemsOption & {
|
|
61
|
+
category: TCategory;
|
|
62
|
+
containedItemCategory?: TContainedItemCategory;
|
|
63
|
+
}): FetchItemResult<Item<TCategory, ContainedItemCategoryFromOption<TCategory, TContainedItemCategory>, FetchItemLanguages<TLanguages>>>;
|
|
64
|
+
declare function fetchItem<const TCategory extends ItemContainerCategory, const TContainedItemCategory extends ContainedItemCategoryOption<TCategory> | undefined = undefined, const TLanguages extends ReadonlyArray<string> | undefined = undefined>(uuid: string, options: FetchItemBaseOptions<TLanguages> & {
|
|
65
|
+
category: TCategory;
|
|
66
|
+
containedItemCategory?: TContainedItemCategory;
|
|
67
|
+
shouldOmitEmbeddedItems: true;
|
|
68
|
+
}): FetchItemResult<ItemWithoutEmbeddedItems<TCategory, ContainedItemCategoryFromOption<TCategory, TContainedItemCategory>, FetchItemLanguages<TLanguages>>>;
|
|
69
|
+
declare function fetchItem<const TCategory extends ItemCategory, const TLanguages extends ReadonlyArray<string> | undefined = undefined>(uuid: string, options: FetchItemBaseOptions<TLanguages> & FetchItemNoOmitEmbeddedItemsOption & {
|
|
70
|
+
category: TCategory;
|
|
71
|
+
containedItemCategory?: never;
|
|
72
|
+
}): FetchItemResult<Item<TCategory, ContainedItemCategory<TCategory>, FetchItemLanguages<TLanguages>>>;
|
|
73
|
+
//#endregion
|
|
74
|
+
export { defineLanguages, fetchItem, withLanguages };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { XML_PARSER_OPTIONS } from "../constants.mjs";
|
|
2
|
+
import { createSchemaValidationError, getErrorOutput, stringLiteral } from "../utils.mjs";
|
|
3
|
+
import { iso639_3Schema, uuidSchema } from "../schemas.mjs";
|
|
4
|
+
import { restoreXMLMetadata } from "../xml/metadata.mjs";
|
|
5
|
+
import { parseItem } from "../parsers/index.mjs";
|
|
6
|
+
import { XMLData } from "../xml/schemas.mjs";
|
|
7
|
+
import { parseWebpageView } from "../parsers/website/index.mjs";
|
|
8
|
+
import * as v from "valibot";
|
|
9
|
+
import { XMLParser } from "fast-xml-parser";
|
|
10
|
+
//#region src/fetchers/item.ts
|
|
11
|
+
function isItemContainerCategory(category) {
|
|
12
|
+
return category === "tree" || category === "set";
|
|
13
|
+
}
|
|
14
|
+
function isItemContainer(item) {
|
|
15
|
+
return isItemContainerCategory(item.category);
|
|
16
|
+
}
|
|
17
|
+
function assertItemCategoryAllowed(category, containedItemCategory) {
|
|
18
|
+
if (category == null || containedItemCategory == null || isItemContainerCategory(category)) return;
|
|
19
|
+
throw new Error(`containedItemCategory can only be used when category is "tree" or "set"; received category "${category}"`);
|
|
20
|
+
}
|
|
21
|
+
function assertShouldOmitEmbeddedItemsAllowed(category, shouldOmitEmbeddedItems) {
|
|
22
|
+
if (!shouldOmitEmbeddedItems || category == null || isItemContainerCategory(category)) return;
|
|
23
|
+
throw new Error(`shouldOmitEmbeddedItems can only be used when category is "tree" or "set"; received category "${category}"`);
|
|
24
|
+
}
|
|
25
|
+
function normalizeFetchedCategory(category) {
|
|
26
|
+
switch (category) {
|
|
27
|
+
case "tree":
|
|
28
|
+
case "bibliography":
|
|
29
|
+
case "concept":
|
|
30
|
+
case "spatialUnit":
|
|
31
|
+
case "period":
|
|
32
|
+
case "person":
|
|
33
|
+
case "propertyVariable":
|
|
34
|
+
case "propertyValue":
|
|
35
|
+
case "resource":
|
|
36
|
+
case "text":
|
|
37
|
+
case "set": return category;
|
|
38
|
+
case "variable": return "propertyVariable";
|
|
39
|
+
case "value": return "propertyValue";
|
|
40
|
+
default: return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function inferFetchItemCategory(rawOchre) {
|
|
44
|
+
const metadataCategory = normalizeFetchedCategory(rawOchre.metadata.item?.category);
|
|
45
|
+
if (metadataCategory != null) return metadataCategory;
|
|
46
|
+
if ("tree" in rawOchre) return "tree";
|
|
47
|
+
if ("bibliography" in rawOchre) return "bibliography";
|
|
48
|
+
if ("concept" in rawOchre) return "concept";
|
|
49
|
+
if ("spatialUnit" in rawOchre) return "spatialUnit";
|
|
50
|
+
if ("period" in rawOchre) return "period";
|
|
51
|
+
if ("person" in rawOchre) return "person";
|
|
52
|
+
if ("propertyVariable" in rawOchre || "variable" in rawOchre) return "propertyVariable";
|
|
53
|
+
if ("propertyValue" in rawOchre || "value" in rawOchre) return "propertyValue";
|
|
54
|
+
if ("resource" in rawOchre) return "resource";
|
|
55
|
+
if ("text" in rawOchre) return "text";
|
|
56
|
+
if ("set" in rawOchre) return "set";
|
|
57
|
+
throw new Error("Could not infer OCHRE item category", { cause: rawOchre });
|
|
58
|
+
}
|
|
59
|
+
function buildOmitEmbeddedItemsXQuery(uuid) {
|
|
60
|
+
return `xquery version "1.0-ml";
|
|
61
|
+
|
|
62
|
+
let $ochre := doc()/ochre[@uuid=${stringLiteral(uuid)}][1]
|
|
63
|
+
return
|
|
64
|
+
if (empty($ochre)) then ()
|
|
65
|
+
else element ochre {
|
|
66
|
+
$ochre/@*,
|
|
67
|
+
for $node in $ochre/node()
|
|
68
|
+
return
|
|
69
|
+
if (local-name($node) = ("tree", "set"))
|
|
70
|
+
then element { node-name($node) } { $node/@*, $node/node()[not(self::items)] }
|
|
71
|
+
else $node
|
|
72
|
+
}`;
|
|
73
|
+
}
|
|
74
|
+
function omitEmbeddedItems(item) {
|
|
75
|
+
const { items: _items, ...itemWithoutEmbeddedItems } = item;
|
|
76
|
+
return itemWithoutEmbeddedItems;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Validate language codes while preserving literal tuple inference.
|
|
80
|
+
*/
|
|
81
|
+
function parseLanguages(languages) {
|
|
82
|
+
const parsedLanguages = [];
|
|
83
|
+
for (const language of languages) parsedLanguages.push(v.parse(iso639_3Schema, language));
|
|
84
|
+
return parsedLanguages;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Defines a reusable languages tuple with validation and literal type inference.
|
|
88
|
+
*
|
|
89
|
+
* Inline arrays can be passed directly to fetchItem:
|
|
90
|
+
* `fetchItem(uuid, { languages: ["eng", "spa"] })`.
|
|
91
|
+
*
|
|
92
|
+
* Use this helper when the language set is stored separately:
|
|
93
|
+
* `const languages = defineLanguages("eng", "spa")`.
|
|
94
|
+
*/
|
|
95
|
+
function defineLanguages(...languages) {
|
|
96
|
+
return parseLanguages(languages);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* @deprecated Pass inline language arrays directly to fetchItem, or use
|
|
100
|
+
* defineLanguages("eng", "spa") for reusable language tuples.
|
|
101
|
+
*/
|
|
102
|
+
function withLanguages(languages) {
|
|
103
|
+
return parseLanguages(languages);
|
|
104
|
+
}
|
|
105
|
+
async function fetchItem(uuid, options) {
|
|
106
|
+
try {
|
|
107
|
+
const parsedUuid = v.parse(uuidSchema, uuid);
|
|
108
|
+
assertItemCategoryAllowed(options?.category, options?.containedItemCategory);
|
|
109
|
+
const shouldOmitEmbeddedItems = options?.shouldOmitEmbeddedItems === true;
|
|
110
|
+
assertShouldOmitEmbeddedItemsAllowed(options?.category, shouldOmitEmbeddedItems);
|
|
111
|
+
const languages = options?.languages == null ? [] : parseLanguages(options.languages);
|
|
112
|
+
const fetcher = options?.fetch ?? fetch;
|
|
113
|
+
const response = shouldOmitEmbeddedItems ? await fetcher("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
|
|
114
|
+
method: "POST",
|
|
115
|
+
body: buildOmitEmbeddedItemsXQuery(parsedUuid),
|
|
116
|
+
headers: { "Content-Type": "application/xquery" }
|
|
117
|
+
}) : await fetcher(`https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?uuid=${parsedUuid}&xsl=none&lang="*"`);
|
|
118
|
+
if (!response.ok) throw new Error("Failed to fetch OCHRE data", { cause: response.statusText });
|
|
119
|
+
const dataRaw = await response.text();
|
|
120
|
+
const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
|
|
121
|
+
const { success, issues, output } = v.safeParse(XMLData, data);
|
|
122
|
+
if (!success) throw createSchemaValidationError("Failed to parse OCHRE data", issues);
|
|
123
|
+
restoreXMLMetadata(output, data);
|
|
124
|
+
const category = options?.category ?? inferFetchItemCategory(output.result.ochre);
|
|
125
|
+
assertItemCategoryAllowed(category, options?.containedItemCategory);
|
|
126
|
+
assertShouldOmitEmbeddedItemsAllowed(category, shouldOmitEmbeddedItems);
|
|
127
|
+
const parsedItem = parseItem(output, {
|
|
128
|
+
category,
|
|
129
|
+
containedItemCategory: options?.containedItemCategory,
|
|
130
|
+
languages,
|
|
131
|
+
parseResourceView: (view, context) => parseWebpageView(view, { languages: context.metadata.languages }, context)
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
item: shouldOmitEmbeddedItems && isItemContainer(parsedItem) ? omitEmbeddedItems(parsedItem) : parsedItem,
|
|
135
|
+
error: null,
|
|
136
|
+
detailedError: null
|
|
137
|
+
};
|
|
138
|
+
} catch (error) {
|
|
139
|
+
return {
|
|
140
|
+
item: null,
|
|
141
|
+
...getErrorOutput(error, "Unknown error")
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
//#endregion
|
|
146
|
+
export { defineLanguages, fetchItem, withLanguages };
|