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.
Files changed (84) hide show
  1. package/README.md +31 -3
  2. package/dist/_virtual/_rolldown/runtime.mjs +13 -0
  3. package/dist/categories.d.mts +249 -0
  4. package/dist/categories.mjs +259 -0
  5. package/dist/constants.d.mts +14 -0
  6. package/dist/constants.mjs +18 -1
  7. package/dist/errors.d.mts +23 -0
  8. package/dist/{utilities.mjs → errors.mjs} +33 -84
  9. package/dist/fetchers/gallery.mjs +15 -51
  10. package/dist/fetchers/item-children.mjs +20 -66
  11. package/dist/fetchers/item-links.mjs +26 -79
  12. package/dist/fetchers/item-ocr-data.d.mts +2 -2
  13. package/dist/fetchers/item-ocr-data.mjs +11 -15
  14. package/dist/fetchers/item.d.mts +0 -15
  15. package/dist/fetchers/item.mjs +20 -72
  16. package/dist/fetchers/request.d.mts +70 -0
  17. package/dist/fetchers/request.mjs +100 -0
  18. package/dist/fetchers/set/items.mjs +27 -73
  19. package/dist/fetchers/set/property-values.d.mts +2 -3
  20. package/dist/fetchers/set/property-values.mjs +96 -130
  21. package/dist/fetchers/website-metadata.mjs +35 -57
  22. package/dist/fetchers/website.d.mts +2 -3
  23. package/dist/fetchers/website.mjs +22 -31
  24. package/dist/getters.d.mts +78 -148
  25. package/dist/getters.mjs +127 -208
  26. package/dist/helpers.d.mts +0 -4
  27. package/dist/helpers.mjs +19 -6
  28. package/dist/index.d.mts +8 -6
  29. package/dist/index.mjs +6 -4
  30. package/dist/ocr.d.mts +37 -0
  31. package/dist/ocr.mjs +52 -0
  32. package/dist/parsers/helpers.d.mts +21 -1
  33. package/dist/parsers/helpers.mjs +26 -6
  34. package/dist/parsers/index.d.mts +0 -8
  35. package/dist/parsers/index.mjs +118 -259
  36. package/dist/parsers/languages.d.mts +72 -0
  37. package/dist/parsers/languages.mjs +132 -0
  38. package/dist/parsers/multilingual.d.mts +49 -74
  39. package/dist/parsers/multilingual.mjs +88 -189
  40. package/dist/parsers/property-token.d.mts +34 -0
  41. package/dist/parsers/property-token.mjs +29 -0
  42. package/dist/parsers/string.d.mts +19 -0
  43. package/dist/parsers/string.mjs +45 -25
  44. package/dist/parsers/website/bounds.d.mts +10 -0
  45. package/dist/parsers/website/bounds.mjs +28 -0
  46. package/dist/parsers/website/components.d.mts +91 -0
  47. package/dist/parsers/website/components.mjs +681 -0
  48. package/dist/parsers/website/index.d.mts +0 -7
  49. package/dist/parsers/website/index.mjs +92 -1153
  50. package/dist/parsers/website/links.d.mts +36 -0
  51. package/dist/parsers/website/links.mjs +58 -0
  52. package/dist/parsers/website/messages.d.mts +24 -0
  53. package/dist/parsers/website/messages.mjs +31 -0
  54. package/dist/parsers/website/options.d.mts +6 -0
  55. package/dist/parsers/website/options.mjs +114 -0
  56. package/dist/parsers/website/properties.d.mts +12 -0
  57. package/dist/parsers/website/properties.mjs +158 -0
  58. package/dist/parsers/website/reader.d.mts +54 -4
  59. package/dist/parsers/website/reader.mjs +65 -20
  60. package/dist/parsers/website/slug.d.mts +64 -0
  61. package/dist/parsers/website/slug.mjs +82 -0
  62. package/dist/parsers/website/styles.d.mts +28 -0
  63. package/dist/parsers/website/styles.mjs +103 -0
  64. package/dist/parsers/website/walk.d.mts +68 -0
  65. package/dist/parsers/website/walk.mjs +116 -0
  66. package/dist/query.d.mts +66 -18
  67. package/dist/query.mjs +202 -48
  68. package/dist/reflection.d.mts +64 -0
  69. package/dist/reflection.mjs +79 -0
  70. package/dist/schemas.d.mts +7 -0
  71. package/dist/schemas.mjs +12 -3
  72. package/dist/types/index.d.mts +1 -31
  73. package/dist/types/utilities.d.mts +9 -0
  74. package/dist/types/utilities.mjs +1 -0
  75. package/dist/types/website.d.mts +49 -58
  76. package/dist/xml/metadata.d.mts +16 -0
  77. package/dist/xml/metadata.mjs +32 -11
  78. package/dist/xml/schemas.d.mts +5970 -3
  79. package/dist/xml/schemas.mjs +43 -45
  80. package/dist/xml/types.d.mts +13 -30
  81. package/dist/xquery.d.mts +46 -0
  82. package/dist/xquery.mjs +66 -0
  83. package/package.json +3 -3
  84. 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/utilities.ts
3
- const PSEUDO_UUID_REGEX = /^[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/i;
4
- function isRecord(value) {
5
- return typeof value === "object" && value != null;
6
- }
7
- function isSchemaValidationIssue(value) {
8
- if (!isRecord(value)) return false;
9
- return typeof value.kind === "string" && typeof value.type === "string" && typeof value.message === "string";
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
- if (!Array.isArray(value) || value.length === 0) return false;
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 Object.entries(value)) {
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 (isRecord(value)) return formatCauseRecord(value);
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 { NOT_SUPPLEMENTAL_PREDICATE, SUPPLEMENTAL_XQUERY_PROLOG, createSchemaValidationError, flattenProperties, getErrorOutput, isPseudoUuid, omitSupplemental, stringLiteral };
164
+ export { createSchemaValidationError, getErrorOutput };
@@ -1,48 +1,17 @@
1
- import { DEFAULT_LANGUAGES, XML_PARSER_OPTIONS } from "../constants.mjs";
2
- import { SUPPLEMENTAL_XQUERY_PROLOG, createSchemaValidationError, getErrorOutput, omitSupplemental, stringLiteral } from "../utilities.mjs";
3
- import { gallerySchema, iso639_3Schema } from "../schemas.mjs";
4
- import { restoreXMLMetadata } from "../xml/metadata.mjs";
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 `xquery version "1.0-ml";
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 == null ? [] : parseLanguages(options.languages);
67
- const response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
68
- method: "POST",
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
- headers: { "Content-Type": "application/xquery" }
43
+ schema: XMLGalleryData,
44
+ label: "OCHRE gallery",
45
+ options
76
46
  });
77
- if (!response.ok) throw new Error("Error fetching gallery items, please try again later.", { cause: response.statusText });
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 { DEFAULT_LANGUAGES, XML_PARSER_OPTIONS } from "../constants.mjs";
2
- import { SUPPLEMENTAL_XQUERY_PROLOG, createSchemaValidationError, getErrorOutput, omitSupplemental, stringLiteral } from "../utilities.mjs";
3
- import { iso639_3Schema, uuidSchema } from "../schemas.mjs";
4
- import { restoreXMLMetadata } from "../xml/metadata.mjs";
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 ? ITEM_COLLECTION_CATEGORIES : typeof category === "string" ? [category] : category;
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 `xquery version "1.0-ml";
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 == null ? [] : parseLanguages(options.languages);
102
- const response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
103
- method: "POST",
104
- body: buildXQuery(parsedUuid, options?.category),
105
- headers: { "Content-Type": "application/xquery" }
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
- if (!response.ok) throw new Error("Failed to fetch OCHRE item children", { cause: response.statusText });
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 { DEFAULT_LANGUAGES, XML_PARSER_OPTIONS } from "../constants.mjs";
2
- import { SUPPLEMENTAL_XQUERY_PROLOG, createSchemaValidationError, getErrorOutput, omitSupplemental, stringLiteral } from "../utilities.mjs";
3
- import { iso639_3Schema, uuidSchema } from "../schemas.mjs";
4
- import { restoreXMLMetadata } from "../xml/metadata.mjs";
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 xquery = `let $item-uuid := ${stringLiteral(uuid)}
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/resource")/ochre[@uuid = $item-uuid]/resource,
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(`for $link at $position in $link-nodes
69
- let $uuid := $link/@uuid/string()
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 == null ? [] : parseLanguages(options.languages);
96
- const response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
97
- method: "POST",
98
- body: buildXQuery(parsedUuid),
99
- headers: { "Content-Type": "application/xquery" }
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
- if (!response.ok) throw new Error("Failed to fetch OCHRE item links", { cause: response.statusText });
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 { XML_PARSER_OPTIONS } from "../constants.mjs";
2
- import { createSchemaValidationError, getErrorOutput, stringLiteral } from "../utilities.mjs";
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 $ochre//*[lower-case(local-name(.)) = "ocr"]//*[lower-case(local-name(.)) = "string"][@CONTENT]
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 response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
141
- method: "POST",
142
- body: xquery,
143
- headers: { "Content-Type": "application/xquery" }
144
- });
145
- if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`, { cause: response.statusText });
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 {
@@ -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
  *