ochre-sdk 1.0.72 → 1.0.74
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 +50 -0
- package/dist/constants.mjs +0 -5
- package/dist/fetchers/gallery.mjs +11 -5
- package/dist/fetchers/item-children.mjs +4 -2
- package/dist/fetchers/item-links.mjs +10 -5
- package/dist/fetchers/item-ocr-data.d.mts +37 -0
- package/dist/fetchers/item-ocr-data.mjs +166 -0
- package/dist/fetchers/item.mjs +43 -58
- package/dist/fetchers/set/items.mjs +14 -16
- package/dist/fetchers/set/property-values.mjs +12 -18
- package/dist/fetchers/website-metadata.mjs +7 -3
- package/dist/fetchers/website.mjs +22 -5
- package/dist/getters.d.mts +6 -2
- package/dist/helpers.d.mts +1 -5
- package/dist/helpers.mjs +1 -5
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +3 -3
- package/dist/parsers/index.d.mts +3 -10
- package/dist/parsers/index.mjs +1 -75
- package/dist/parsers/multilingual.d.mts +12 -4
- package/dist/parsers/website/index.mjs +13 -12
- package/dist/query.d.mts +21 -29
- package/dist/query.mjs +219 -86
- package/dist/schemas.d.mts +7 -8
- package/dist/schemas.mjs +7 -9
- package/dist/types/index.d.mts +27 -56
- package/dist/utilities.d.mts +25 -1
- package/dist/utilities.mjs +41 -1
- package/dist/xml/schemas.d.mts +2 -7
- package/dist/xml/schemas.mjs +1 -39
- package/dist/xml/types.d.mts +1 -44
- package/package.json +5 -4
- package/dist/fetchers/ocr-matches.d.mts +0 -44
- package/dist/fetchers/ocr-matches.mjs +0 -134
package/dist/utilities.mjs
CHANGED
|
@@ -151,6 +151,46 @@ function stringLiteral(value) {
|
|
|
151
151
|
return `"${value.replaceAll("\"", "\"\"")}"`;
|
|
152
152
|
}
|
|
153
153
|
/**
|
|
154
|
+
* XQuery prolog declaring `local:omit-supplemental`, which drops every element
|
|
155
|
+
* carrying `supplemental="true"` from a node sequence, at any depth.
|
|
156
|
+
*
|
|
157
|
+
* Subtrees without a supplemental descendant are returned by reference, so
|
|
158
|
+
* nodes are only copied along the path leading to an omitted element. The
|
|
159
|
+
* lookahead walks the attribute axis (`//@supplemental`) rather than testing
|
|
160
|
+
* every element, which measures around three times faster on large documents.
|
|
161
|
+
*
|
|
162
|
+
* Must be declared before any query body that calls {@link omitSupplemental}.
|
|
163
|
+
*/
|
|
164
|
+
const SUPPLEMENTAL_XQUERY_PROLOG = `declare function local:omit-supplemental($nodes as node()*) as node()* {
|
|
165
|
+
for $node in $nodes
|
|
166
|
+
return
|
|
167
|
+
if ($node instance of element())
|
|
168
|
+
then
|
|
169
|
+
if ($node/@supplemental = "true")
|
|
170
|
+
then ()
|
|
171
|
+
else if (empty($node//@supplemental[. = "true"]))
|
|
172
|
+
then $node
|
|
173
|
+
else element { node-name($node) } {
|
|
174
|
+
$node/@*,
|
|
175
|
+
local:omit-supplemental($node/node())
|
|
176
|
+
}
|
|
177
|
+
else $node
|
|
178
|
+
};`;
|
|
179
|
+
/**
|
|
180
|
+
* Wrap an XQuery node expression so supplemental nodes are omitted from it
|
|
181
|
+
* @param expression - The XQuery expression returning the nodes to filter
|
|
182
|
+
* @returns The wrapped XQuery expression
|
|
183
|
+
*/
|
|
184
|
+
function omitSupplemental(expression) {
|
|
185
|
+
return `local:omit-supplemental(${expression})`;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* XQuery predicate keeping only nodes that are neither supplemental themselves
|
|
189
|
+
* nor nested inside a supplemental node. Use it when aggregating over nodes
|
|
190
|
+
* instead of returning them.
|
|
191
|
+
*/
|
|
192
|
+
const NOT_SUPPLEMENTAL_PREDICATE = "[not(ancestor-or-self::*[@supplemental = \"true\"])]";
|
|
193
|
+
/**
|
|
154
194
|
* Flatten a properties array
|
|
155
195
|
* @param properties - The properties to flatten
|
|
156
196
|
* @returns The flattened properties
|
|
@@ -169,4 +209,4 @@ function flattenProperties(properties) {
|
|
|
169
209
|
return result;
|
|
170
210
|
}
|
|
171
211
|
//#endregion
|
|
172
|
-
export { createSchemaValidationError, flattenProperties, getErrorOutput, isPseudoUuid, stringLiteral };
|
|
212
|
+
export { NOT_SUPPLEMENTAL_PREDICATE, SUPPLEMENTAL_XQUERY_PROLOG, createSchemaValidationError, flattenProperties, getErrorOutput, isPseudoUuid, omitSupplemental, stringLiteral };
|
package/dist/xml/schemas.d.mts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import { XMLData as XMLData$1, XMLDataItem as XMLDataItem$1, XMLGalleryData as XMLGalleryData$1, XMLItemLinksData as XMLItemLinksData$1, XMLLink as XMLLink$1,
|
|
1
|
+
import { XMLData as XMLData$1, XMLDataItem as XMLDataItem$1, XMLGalleryData as XMLGalleryData$1, XMLItemLinksData as XMLItemLinksData$1, XMLLink as XMLLink$1, XMLSetItemsData as XMLSetItemsData$1, XMLWebsiteData as XMLWebsiteData$1 } from "./types.mjs";
|
|
2
2
|
import * as v from "valibot";
|
|
3
3
|
//#region src/xml/schemas.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Schema for validating OCR matches fetched from the OCHRE API
|
|
6
|
-
* @internal
|
|
7
|
-
*/
|
|
8
|
-
declare const XMLOcrMatchesData: v.GenericSchema<unknown, XMLOcrMatchesData$1>;
|
|
9
4
|
declare const XMLLink: v.GenericSchema<unknown, XMLLink$1>;
|
|
10
5
|
declare const XMLDataItem: v.GenericSchema<unknown, XMLDataItem$1>;
|
|
11
6
|
declare const XMLItemLinksData: v.GenericSchema<unknown, XMLItemLinksData$1>;
|
|
@@ -14,4 +9,4 @@ declare const XMLSetItemsData: v.GenericSchema<unknown, XMLSetItemsData$1>;
|
|
|
14
9
|
declare const XMLData: v.GenericSchema<unknown, XMLData$1>;
|
|
15
10
|
declare const XMLWebsiteData: v.GenericSchema<unknown, XMLWebsiteData$1>;
|
|
16
11
|
//#endregion
|
|
17
|
-
export { XMLData, XMLDataItem, XMLGalleryData, XMLItemLinksData, XMLLink,
|
|
12
|
+
export { XMLData, XMLDataItem, XMLGalleryData, XMLItemLinksData, XMLLink, XMLSetItemsData, XMLWebsiteData };
|
package/dist/xml/schemas.mjs
CHANGED
|
@@ -224,42 +224,6 @@ const XMLImageMap = v.object({
|
|
|
224
224
|
width: XMLNumber,
|
|
225
225
|
height: XMLNumber
|
|
226
226
|
}, "XMLImageMap: Shape error");
|
|
227
|
-
const XMLOcrString = v.object({
|
|
228
|
-
HPOS: XMLNumber,
|
|
229
|
-
VPOS: XMLNumber,
|
|
230
|
-
WIDTH: XMLNumber,
|
|
231
|
-
HEIGHT: XMLNumber,
|
|
232
|
-
CONTENT: v.string("XMLOcrString: CONTENT is string and required"),
|
|
233
|
-
VERTICES: v.optional(v.string("XMLOcrString: VERTICES is string and optional"))
|
|
234
|
-
}, "XMLOcrString: Shape error");
|
|
235
|
-
const XMLOcrTextLine = v.object({ string: v.optional(v.array(XMLOcrString, "XMLOcrTextLine: string is array of XMLOcrString")) }, "XMLOcrTextLine: Shape error");
|
|
236
|
-
const XMLOcrTextBlock = v.object({ TextLine: v.optional(v.array(XMLOcrTextLine, "XMLOcrTextBlock: TextLine is array of XMLOcrTextLine")) }, "XMLOcrTextBlock: Shape error");
|
|
237
|
-
const XMLOcrPage = v.object({
|
|
238
|
-
n: XMLOptionalNumber,
|
|
239
|
-
fileName: v.optional(v.string("XMLOcrPage: fileName is string and optional")),
|
|
240
|
-
WIDTH: XMLOptionalNumber,
|
|
241
|
-
HEIGHT: XMLOptionalNumber,
|
|
242
|
-
TextBlock: v.optional(v.array(XMLOcrTextBlock, "XMLOcrPage: TextBlock is array of XMLOcrTextBlock"))
|
|
243
|
-
}, "XMLOcrPage: Shape error");
|
|
244
|
-
const XMLOcr = v.object({ Page: v.optional(v.array(XMLOcrPage, "XMLOcr: Page is array of XMLOcrPage")) }, "XMLOcr: Shape error");
|
|
245
|
-
const XMLOcrMatch = v.object({
|
|
246
|
-
resourceUuid: v.optional(v.string("XMLOcrMatch: resourceUuid is string and optional")),
|
|
247
|
-
n: XMLOptionalNumber,
|
|
248
|
-
fileName: v.optional(v.string("XMLOcrMatch: fileName is string and optional")),
|
|
249
|
-
WIDTH: XMLOptionalNumber,
|
|
250
|
-
HEIGHT: XMLOptionalNumber,
|
|
251
|
-
string: v.optional(v.array(XMLOcrString, "XMLOcrMatch: string is array of XMLOcrString"))
|
|
252
|
-
}, "XMLOcrMatch: Shape error");
|
|
253
|
-
const XMLOcrMatchItem = v.object({
|
|
254
|
-
uuid: v.pipe(v.string("XMLOcrMatchItem: uuid is string and required"), v.check(isPseudoUuid, "XMLOcrMatchItem: uuid is not a valid pseudo-UUID")),
|
|
255
|
-
matchCount: XMLNumber,
|
|
256
|
-
ocrMatch: v.optional(v.array(XMLOcrMatch, "XMLOcrMatchItem: ocrMatch is array of XMLOcrMatch"))
|
|
257
|
-
}, "XMLOcrMatchItem: Shape error");
|
|
258
|
-
/**
|
|
259
|
-
* Schema for validating OCR matches fetched from the OCHRE API
|
|
260
|
-
* @internal
|
|
261
|
-
*/
|
|
262
|
-
const XMLOcrMatchesData = v.object({ result: v.object({ ochre: v.object({ ocrMatches: v.optional(v.object({ ocrItem: v.optional(v.array(XMLOcrMatchItem, "XMLOcrMatchesData: ocrItem is array of XMLOcrMatchItem")) })) }) }) }, "XMLOcrMatchesData: Shape error");
|
|
263
227
|
const XMLNote = v.object({
|
|
264
228
|
content: v.optional(XMLContent.entries.content),
|
|
265
229
|
payload: v.optional(v.string("XMLNote: payload is string and optional")),
|
|
@@ -680,7 +644,6 @@ const XMLResource = v.object({
|
|
|
680
644
|
width: XMLOptionalNumber,
|
|
681
645
|
image: v.optional(XMLImage),
|
|
682
646
|
imagemap: v.optional(XMLImageMap),
|
|
683
|
-
ocr: v.optional(XMLOcr),
|
|
684
647
|
document: v.optional(XMLContent),
|
|
685
648
|
coordinates: v.optional(XMLCoordinates),
|
|
686
649
|
periods: v.optional(v.object({ period: v.array(XMLPeriod) })),
|
|
@@ -832,7 +795,6 @@ const XMLWebsiteResource = v.lazy(() => v.object({
|
|
|
832
795
|
width: XMLOptionalNumber,
|
|
833
796
|
image: v.optional(XMLImage),
|
|
834
797
|
imagemap: v.optional(XMLImageMap),
|
|
835
|
-
ocr: v.optional(XMLOcr),
|
|
836
798
|
document: v.optional(XMLContent),
|
|
837
799
|
coordinates: v.optional(XMLCoordinates),
|
|
838
800
|
periods: v.optional(v.object({ period: v.array(XMLPeriod) })),
|
|
@@ -966,4 +928,4 @@ const XMLWebsiteData = v.object({ result: v.object({ ochre: v.object({
|
|
|
966
928
|
tree: v.array(XMLWebsiteTree)
|
|
967
929
|
}, "XMLWebsiteData: ochre is object with website tree") }, "XMLWebsiteData: result is object with ochre") }, "XMLWebsiteData: Shape error");
|
|
968
930
|
//#endregion
|
|
969
|
-
export { XMLData, XMLDataItem, XMLGalleryData, XMLItemLinksData, XMLLink,
|
|
931
|
+
export { XMLData, XMLDataItem, XMLGalleryData, XMLItemLinksData, XMLLink, XMLSetItemsData, XMLWebsiteData };
|
package/dist/xml/types.d.mts
CHANGED
|
@@ -182,48 +182,6 @@ type XMLImageMap = {
|
|
|
182
182
|
width: XMLNumber;
|
|
183
183
|
height: XMLNumber;
|
|
184
184
|
};
|
|
185
|
-
type XMLOcrString = {
|
|
186
|
-
HPOS: XMLNumber;
|
|
187
|
-
VPOS: XMLNumber;
|
|
188
|
-
WIDTH: XMLNumber;
|
|
189
|
-
HEIGHT: XMLNumber;
|
|
190
|
-
CONTENT: string;
|
|
191
|
-
VERTICES?: string;
|
|
192
|
-
};
|
|
193
|
-
type XMLOcrTextLine = {
|
|
194
|
-
string?: Array<XMLOcrString>;
|
|
195
|
-
};
|
|
196
|
-
type XMLOcrTextBlock = {
|
|
197
|
-
TextLine?: Array<XMLOcrTextLine>;
|
|
198
|
-
};
|
|
199
|
-
type XMLOcrPage = {
|
|
200
|
-
n?: XMLNumber;
|
|
201
|
-
fileName?: string;
|
|
202
|
-
WIDTH?: XMLNumber;
|
|
203
|
-
HEIGHT?: XMLNumber;
|
|
204
|
-
TextBlock?: Array<XMLOcrTextBlock>;
|
|
205
|
-
};
|
|
206
|
-
type XMLOcr = {
|
|
207
|
-
Page?: Array<XMLOcrPage>;
|
|
208
|
-
};
|
|
209
|
-
type XMLOcrMatch = Omit<XMLOcrPage, "TextBlock"> & {
|
|
210
|
-
resourceUuid?: string;
|
|
211
|
-
string?: Array<XMLOcrString>;
|
|
212
|
-
};
|
|
213
|
-
type XMLOcrMatchItem = {
|
|
214
|
-
uuid: string;
|
|
215
|
-
matchCount: XMLNumber;
|
|
216
|
-
ocrMatch?: Array<XMLOcrMatch>;
|
|
217
|
-
};
|
|
218
|
-
type XMLOcrMatchesData = {
|
|
219
|
-
result: {
|
|
220
|
-
ochre: {
|
|
221
|
-
ocrMatches?: {
|
|
222
|
-
ocrItem?: Array<XMLOcrMatchItem>;
|
|
223
|
-
};
|
|
224
|
-
};
|
|
225
|
-
};
|
|
226
|
-
};
|
|
227
185
|
type XMLNote = Partial<XMLContent> & XMLString & {
|
|
228
186
|
noteNo?: XMLNumber;
|
|
229
187
|
title?: string;
|
|
@@ -605,7 +563,6 @@ type XMLResource = XMLBaseItem & {
|
|
|
605
563
|
width?: XMLNumber;
|
|
606
564
|
image?: XMLImage;
|
|
607
565
|
imagemap?: XMLImageMap;
|
|
608
|
-
ocr?: XMLOcr;
|
|
609
566
|
document?: XMLContent;
|
|
610
567
|
coordinates?: XMLCoordinates;
|
|
611
568
|
periods?: {
|
|
@@ -963,4 +920,4 @@ type XMLWebsiteData = {
|
|
|
963
920
|
};
|
|
964
921
|
};
|
|
965
922
|
//#endregion
|
|
966
|
-
export { XMLBaseItem, XMLBibliography, XMLBoolean, XMLConcept, XMLContent, XMLContext, XMLContextGroup, XMLContextItem, XMLContextValue, XMLCoordinate, XMLCoordinates, XMLCoordinatesSource, XMLData, XMLDataItem, XMLDictionaryUnit, XMLEmptyContext, XMLEvent, XMLGallery, XMLGalleryData, XMLHeading, XMLHeadingItemCategory, XMLIdentification, XMLImage, XMLImageMap, XMLImageMapArea, XMLInterpretation, XMLItemCategory, XMLItemLinks, XMLItemLinksData, XMLLicense, XMLLink, XMLLinkedBaseItem, XMLLinkedBibliography, XMLLinkedConcept, XMLLinkedPeriod, XMLLinkedPerson, XMLLinkedPropertyValue, XMLLinkedPropertyVariable, XMLLinkedResource, XMLLinkedSet, XMLLinkedSpatialUnit, XMLLinkedText, XMLLinkedTree, XMLMetadata, XMLNote, XMLNumber, XMLObservation,
|
|
923
|
+
export { XMLBaseItem, XMLBibliography, XMLBoolean, XMLConcept, XMLContent, XMLContext, XMLContextGroup, XMLContextItem, XMLContextValue, XMLCoordinate, XMLCoordinates, XMLCoordinatesSource, XMLData, XMLDataItem, XMLDictionaryUnit, XMLEmptyContext, XMLEvent, XMLGallery, XMLGalleryData, XMLHeading, XMLHeadingItemCategory, XMLIdentification, XMLImage, XMLImageMap, XMLImageMapArea, XMLInterpretation, XMLItemCategory, XMLItemLinks, XMLItemLinksData, XMLLicense, XMLLink, XMLLinkedBaseItem, XMLLinkedBibliography, XMLLinkedConcept, XMLLinkedPeriod, XMLLinkedPerson, XMLLinkedPropertyValue, XMLLinkedPropertyVariable, XMLLinkedResource, XMLLinkedSet, XMLLinkedSpatialUnit, XMLLinkedText, XMLLinkedTree, XMLMetadata, XMLNote, XMLNumber, XMLObservation, XMLPeriod, XMLPerson, XMLProperty, XMLPropertyRelation, XMLPropertyValue, XMLPropertyVariable, XMLRecursiveItemCategory, XMLResource, XMLRichTextEnvelope, XMLSection, XMLSet, XMLSetItems, XMLSetItemsData, XMLSimplifiedProperty, XMLSpatialUnit, XMLString, XMLText, XMLTree, XMLWebsiteContext, XMLWebsiteContextItem, XMLWebsiteContextLevel, XMLWebsiteData, XMLWebsiteFilterContext, XMLWebsiteFilterContextItem, XMLWebsiteOptions, XMLWebsiteProperties, XMLWebsiteResource, XMLWebsiteResourceGroup, XMLWebsiteResourceItem, XMLWebsiteScope, XMLWebsiteSegment, XMLWebsiteStyle, XMLWebsiteTree };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ochre-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.74",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Node.js library for working with OCHRE (Online Cultural and Historical Research Environment) data",
|
|
@@ -51,11 +51,12 @@
|
|
|
51
51
|
"valibot": "^1.4.2"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@antfu/eslint-config": "^9.
|
|
54
|
+
"@antfu/eslint-config": "^9.3.0",
|
|
55
55
|
"@types/node": "^24.13.3",
|
|
56
|
-
"bumpp": "^12.
|
|
56
|
+
"bumpp": "^12.2.0",
|
|
57
57
|
"eslint": "^10.8.0",
|
|
58
|
-
"
|
|
58
|
+
"eslint-plugin-erasable-syntax-only": "^0.4.2",
|
|
59
|
+
"knip": "^6.32.0",
|
|
59
60
|
"oxfmt": "^0.62.0",
|
|
60
61
|
"tsdown": "^0.22.14",
|
|
61
62
|
"typescript": "^6.0.3",
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { OcrMatch } from "../types/index.mjs";
|
|
2
|
-
import { FetchFunction } from "../parsers/helpers.mjs";
|
|
3
|
-
//#region src/fetchers/ocr-matches.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Fetches the locations of OCR text matches within OCHRE resources
|
|
6
|
-
*
|
|
7
|
-
* Matching mirrors the `ocr` Set item query target, so the same value and match
|
|
8
|
-
* mode that selected an item will locate its hits. `matchCountsByUuid` reports
|
|
9
|
-
* the untruncated count, which can exceed the returned matches when
|
|
10
|
-
* `maxMatchesPerItem` caps them.
|
|
11
|
-
*
|
|
12
|
-
* @param parameters - The parameters for the fetch
|
|
13
|
-
* @param parameters.uuids - The resource UUIDs to search, typically from a filtered Set item fetch
|
|
14
|
-
* @param parameters.value - The search value
|
|
15
|
-
* @param parameters.matchMode - Whether to match loosely (stemming and wildcards) or on whole OCR words, defaults to "includes"
|
|
16
|
-
* @param parameters.isCaseSensitive - Whether matching is case sensitive, defaults to false
|
|
17
|
-
* @param parameters.maxMatchesPerItem - The cap on returned matches per requested UUID, defaults to 50
|
|
18
|
-
* @param options - Options for the fetch
|
|
19
|
-
* @param options.fetch - The fetch function to use
|
|
20
|
-
* @returns The OCR matches, or null if the fetch/parse fails
|
|
21
|
-
*/
|
|
22
|
-
declare function fetchOcrMatches(parameters: {
|
|
23
|
-
uuids: Array<string>;
|
|
24
|
-
value: string;
|
|
25
|
-
matchMode?: "includes" | "exact";
|
|
26
|
-
isCaseSensitive?: boolean;
|
|
27
|
-
maxMatchesPerItem?: number;
|
|
28
|
-
}, options?: {
|
|
29
|
-
fetch?: FetchFunction;
|
|
30
|
-
}): Promise<{
|
|
31
|
-
matches: Array<OcrMatch>;
|
|
32
|
-
matchesByUuid: Record<string, Array<OcrMatch>>;
|
|
33
|
-
matchCountsByUuid: Record<string, number>;
|
|
34
|
-
error: null;
|
|
35
|
-
detailedError: null;
|
|
36
|
-
} | {
|
|
37
|
-
matches: null;
|
|
38
|
-
matchesByUuid: null;
|
|
39
|
-
matchCountsByUuid: null;
|
|
40
|
-
error: string;
|
|
41
|
-
detailedError: string;
|
|
42
|
-
}>;
|
|
43
|
-
//#endregion
|
|
44
|
-
export { fetchOcrMatches };
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { XML_PARSER_OPTIONS } from "../constants.mjs";
|
|
2
|
-
import { createSchemaValidationError, getErrorOutput, stringLiteral } from "../utilities.mjs";
|
|
3
|
-
import { buildOcrTermQueryExpressions } from "../query.mjs";
|
|
4
|
-
import { ocrMatchesParametersSchema } from "../schemas.mjs";
|
|
5
|
-
import { restoreXMLMetadata } from "../xml/metadata.mjs";
|
|
6
|
-
import { parseOcrMatches } from "../parsers/index.mjs";
|
|
7
|
-
import { XMLOcrMatchesData } from "../xml/schemas.mjs";
|
|
8
|
-
import * as v from "valibot";
|
|
9
|
-
import { XMLParser } from "fast-xml-parser";
|
|
10
|
-
//#region src/fetchers/ocr-matches.ts
|
|
11
|
-
/**
|
|
12
|
-
* Build an XQuery string to fetch OCR match locations from the OCHRE API
|
|
13
|
-
*
|
|
14
|
-
* Each OCR word is matched with `cts:contains` against the same per-term CTS
|
|
15
|
-
* queries the Set item filter compiles, so hit locations always agree with what
|
|
16
|
-
* the filter matched — including stemming and wildcards, which cannot be
|
|
17
|
-
* reproduced outside MarkLogic.
|
|
18
|
-
*
|
|
19
|
-
* @param parameters - The parameters for the fetch
|
|
20
|
-
* @param parameters.uuids - The resource UUIDs to search
|
|
21
|
-
* @param parameters.termQueryExpressions - One CTS query expression per search term, in word order
|
|
22
|
-
* @param parameters.maxMatchesPerItem - The cap on returned matches per requested UUID
|
|
23
|
-
* @returns An XQuery string
|
|
24
|
-
*/
|
|
25
|
-
function buildXQuery(parameters) {
|
|
26
|
-
const { uuids, termQueryExpressions, maxMatchesPerItem } = parameters;
|
|
27
|
-
return `xquery version "1.0-ml";
|
|
28
|
-
|
|
29
|
-
declare variable $uuids := (${Array.from(uuids, (uuid) => stringLiteral(uuid)).join(", ")});
|
|
30
|
-
|
|
31
|
-
declare variable $termQueries := (
|
|
32
|
-
${termQueryExpressions.join(",\n ")}
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
declare variable $termCount := ${termQueryExpressions.length};
|
|
36
|
-
|
|
37
|
-
<ochre>{
|
|
38
|
-
<ocrMatches>{
|
|
39
|
-
for $uuid in $uuids
|
|
40
|
-
let $matches :=
|
|
41
|
-
for $page in doc($uuid)//ocr/Page
|
|
42
|
-
let $words := $page//TextLine/string
|
|
43
|
-
let $wordCount := count($words)
|
|
44
|
-
let $resourceUuid := string($page/ancestor::resource[1]/@uuid)
|
|
45
|
-
for $word at $index in $words
|
|
46
|
-
where $index + $termCount - 1 le $wordCount
|
|
47
|
-
and (every $offset in (1 to $termCount)
|
|
48
|
-
satisfies cts:contains($words[$index + $offset - 1], $termQueries[$offset]))
|
|
49
|
-
return <ocrMatch resourceUuid="{$resourceUuid}">{
|
|
50
|
-
$page/@n, $page/@fileName, $page/@WIDTH, $page/@HEIGHT,
|
|
51
|
-
subsequence($words, $index, $termCount)
|
|
52
|
-
}</ocrMatch>
|
|
53
|
-
return <ocrItem uuid="{$uuid}" matchCount="{count($matches)}">{
|
|
54
|
-
subsequence($matches, 1, ${maxMatchesPerItem})
|
|
55
|
-
}</ocrItem>
|
|
56
|
-
}</ocrMatches>
|
|
57
|
-
}</ochre>`;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Fetches the locations of OCR text matches within OCHRE resources
|
|
61
|
-
*
|
|
62
|
-
* Matching mirrors the `ocr` Set item query target, so the same value and match
|
|
63
|
-
* mode that selected an item will locate its hits. `matchCountsByUuid` reports
|
|
64
|
-
* the untruncated count, which can exceed the returned matches when
|
|
65
|
-
* `maxMatchesPerItem` caps them.
|
|
66
|
-
*
|
|
67
|
-
* @param parameters - The parameters for the fetch
|
|
68
|
-
* @param parameters.uuids - The resource UUIDs to search, typically from a filtered Set item fetch
|
|
69
|
-
* @param parameters.value - The search value
|
|
70
|
-
* @param parameters.matchMode - Whether to match loosely (stemming and wildcards) or on whole OCR words, defaults to "includes"
|
|
71
|
-
* @param parameters.isCaseSensitive - Whether matching is case sensitive, defaults to false
|
|
72
|
-
* @param parameters.maxMatchesPerItem - The cap on returned matches per requested UUID, defaults to 50
|
|
73
|
-
* @param options - Options for the fetch
|
|
74
|
-
* @param options.fetch - The fetch function to use
|
|
75
|
-
* @returns The OCR matches, or null if the fetch/parse fails
|
|
76
|
-
*/
|
|
77
|
-
async function fetchOcrMatches(parameters, options) {
|
|
78
|
-
try {
|
|
79
|
-
const { uuids, value, matchMode, isCaseSensitive, maxMatchesPerItem } = v.parse(ocrMatchesParametersSchema, parameters);
|
|
80
|
-
const termQueryExpressions = buildOcrTermQueryExpressions({
|
|
81
|
-
value,
|
|
82
|
-
matchMode,
|
|
83
|
-
isCaseSensitive
|
|
84
|
-
});
|
|
85
|
-
if (termQueryExpressions.length === 0) return {
|
|
86
|
-
matches: [],
|
|
87
|
-
matchesByUuid: {},
|
|
88
|
-
matchCountsByUuid: {},
|
|
89
|
-
error: null,
|
|
90
|
-
detailedError: null
|
|
91
|
-
};
|
|
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({
|
|
95
|
-
uuids,
|
|
96
|
-
termQueryExpressions,
|
|
97
|
-
maxMatchesPerItem
|
|
98
|
-
}),
|
|
99
|
-
headers: { "Content-Type": "application/xquery" }
|
|
100
|
-
});
|
|
101
|
-
if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`, { 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(XMLOcrMatchesData, data);
|
|
105
|
-
if (!success) throw createSchemaValidationError("Failed to parse OCHRE OCR matches", issues);
|
|
106
|
-
restoreXMLMetadata(output, data);
|
|
107
|
-
const rawOcrItems = output.result.ochre.ocrMatches?.ocrItem ?? [];
|
|
108
|
-
const matches = parseOcrMatches(rawOcrItems);
|
|
109
|
-
const matchesByUuid = {};
|
|
110
|
-
const matchCountsByUuid = {};
|
|
111
|
-
for (const uuid of uuids) {
|
|
112
|
-
matchesByUuid[uuid] = [];
|
|
113
|
-
matchCountsByUuid[uuid] = 0;
|
|
114
|
-
}
|
|
115
|
-
for (const rawOcrItem of rawOcrItems) matchCountsByUuid[rawOcrItem.uuid] = rawOcrItem.matchCount;
|
|
116
|
-
for (const match of matches) matchesByUuid[match.uuid]?.push(match);
|
|
117
|
-
return {
|
|
118
|
-
matches,
|
|
119
|
-
matchesByUuid,
|
|
120
|
-
matchCountsByUuid,
|
|
121
|
-
error: null,
|
|
122
|
-
detailedError: null
|
|
123
|
-
};
|
|
124
|
-
} catch (error) {
|
|
125
|
-
return {
|
|
126
|
-
matches: null,
|
|
127
|
-
matchesByUuid: null,
|
|
128
|
-
matchCountsByUuid: null,
|
|
129
|
-
...getErrorOutput(error, "Unknown error")
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
//#endregion
|
|
134
|
-
export { fetchOcrMatches };
|