ochre-sdk 1.0.69 → 1.0.71

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.
@@ -60,7 +60,8 @@ function collectWebsitePageSlugs(resources, options, slugPrefix, pageSlugsByUuid
60
60
  collectWebsitePageSlugs(resource.resource, options, slugPrefix, pageSlugsByUuid, segmentSlugPrefix);
61
61
  continue;
62
62
  }
63
- if (websitePresentationReader(resource.properties != null ? parseSimplifiedProperties(resource.properties, options) : []).value("presentation") === "page") {
63
+ const resourceProperties = resource.properties != null ? parseSimplifiedProperties(resource.properties, options) : [];
64
+ if (websitePresentationReader(resourceProperties).value("presentation") === "page") {
64
65
  const slug = cleanWebsitePageSlug(resource.slug);
65
66
  if (slug == null) throw new Error(`Slug not found for page (${formatXMLWebsiteResourceMetadata(resource)})`, { cause: resource });
66
67
  const pageSlug = prefixSlug(slug, slugPrefix);
@@ -812,7 +813,8 @@ function parseWebTitle(properties, identification, overrides) {
812
813
  function parseWebElement(elementResource, options, context) {
813
814
  const identification = parseIdentification(elementResource.identification, options);
814
815
  const elementProperties = elementResource.properties?.property ? parseSimplifiedProperties(elementResource.properties, options) : [];
815
- const properties = parseWebElementProperties(websitePresentationReader(websitePresentationReader(elementProperties).requiredProperty("presentation", `Presentation property not found for element (${formatXMLWebsiteResourceMetadata(elementResource)})`).properties).requiredProperty("component", `Component property not found for element (${formatXMLWebsiteResourceMetadata(elementResource)})`), elementResource, options, context);
816
+ const presentationProperty = websitePresentationReader(elementProperties).requiredProperty("presentation", `Presentation property not found for element (${formatXMLWebsiteResourceMetadata(elementResource)})`);
817
+ const properties = parseWebElementProperties(websitePresentationReader(presentationProperty.properties).requiredProperty("component", `Component property not found for element (${formatXMLWebsiteResourceMetadata(elementResource)})`), elementResource, options, context);
816
818
  const cssStyles = parseResponsiveCssStyles(elementProperties);
817
819
  const title = parseWebTitle(elementProperties, identification, {
818
820
  isNameDisplayed: [
@@ -980,7 +982,8 @@ function parseSidebar(resources, options, context) {
980
982
  mobile: []
981
983
  };
982
984
  const sidebarResource = resources.find((resource) => {
983
- const resourceReader = websitePresentationReader(resource.properties ? parseSimplifiedProperties(resource.properties, options) : []);
985
+ const resourceProperties = resource.properties ? parseSimplifiedProperties(resource.properties, options) : [];
986
+ const resourceReader = websitePresentationReader(resourceProperties);
984
987
  return resourceReader.value("presentation") === "element" && resourceReader.nestedByValue("presentation", "element").value("component") === "sidebar";
985
988
  });
986
989
  if (sidebarResource != null) {
@@ -995,7 +998,8 @@ function parseSidebar(resources, options, context) {
995
998
  cssStyles.mobile = parsedCssStyles.mobile;
996
999
  const sidebarResources = sidebarResource.resource ? normalizeWebsiteResources(sidebarResource.resource) : [];
997
1000
  for (const resource of sidebarResources) {
998
- const resourceType = websitePresentationReader(resource.properties ? parseSimplifiedProperties(resource.properties, options) : []).value("presentation");
1001
+ const resourceProperties = resource.properties ? parseSimplifiedProperties(resource.properties, options) : [];
1002
+ const resourceType = websitePresentationReader(resourceProperties).value("presentation");
999
1003
  if (resourceType === null) continue;
1000
1004
  switch (resourceType) {
1001
1005
  case "element": {
@@ -1025,7 +1029,8 @@ function parseWebAccordionItem(elementResource, childResources, options, context
1025
1029
  const trigger = parseWebElement(elementResource, options, context);
1026
1030
  const items = [];
1027
1031
  for (const resource of childResources) {
1028
- const resourceType = websitePresentationReader(resource.properties ? parseSimplifiedProperties(resource.properties, options) : []).value("presentation");
1032
+ const resourceProperties = resource.properties ? parseSimplifiedProperties(resource.properties, options) : [];
1033
+ const resourceType = websitePresentationReader(resourceProperties).value("presentation");
1029
1034
  if (resourceType === null) continue;
1030
1035
  switch (resourceType) {
1031
1036
  case "element": {
@@ -1131,7 +1136,8 @@ function parseWebBlock(blockResource, options, context) {
1131
1136
  const isSupportingAccordionItems = returnBlock.properties.default.layout === "accordion" || returnBlock.properties.tablet?.layout === "accordion" || returnBlock.properties.mobile?.layout === "accordion";
1132
1137
  const blockItems = [];
1133
1138
  for (const resource of blockResources) {
1134
- const resourceReader = websitePresentationReader(resource.properties ? parseSimplifiedProperties(resource.properties, options) : []);
1139
+ const resourceProperties = resource.properties ? parseSimplifiedProperties(resource.properties, options) : [];
1140
+ const resourceReader = websitePresentationReader(resourceProperties);
1135
1141
  const resourceType = resourceReader.value("presentation");
1136
1142
  if (resourceType === null) continue;
1137
1143
  switch (resourceType) {
@@ -1165,7 +1171,8 @@ function parseWebBlock(blockResource, options, context) {
1165
1171
  * @returns Parsed WebsiteProperties object
1166
1172
  */
1167
1173
  function parseWebsiteProperties(properties, websiteTree, sidebar, options, parent) {
1168
- const websiteReader = websitePresentationReader(parseSimplifiedProperties({ property: properties }, options)).nested("presentation");
1174
+ const mainProperties = parseSimplifiedProperties({ property: properties }, options);
1175
+ const websiteReader = websitePresentationReader(mainProperties).nested("presentation");
1169
1176
  const returnProperties = {
1170
1177
  type: websiteReader.valueOr("webUI", parent?.type ?? "traditional"),
1171
1178
  status: websiteReader.valueOr("status", parent?.status ?? "development"),
package/dist/query.d.mts CHANGED
@@ -1,12 +1,40 @@
1
1
  import { Query } from "./types/index.mjs";
2
2
  //#region src/query.d.ts
3
+ /**
4
+ * Error message for OCR queries nested inside an OR group
5
+ * @internal
6
+ */
7
+ declare const OCR_DISJUNCTION_ERROR_MESSAGE = "OCR queries cannot be nested inside an OR group because they are resolved by a document join instead of a CTS query";
8
+ type OcrUuidBinding = {
9
+ name: string;
10
+ expression: string;
11
+ };
3
12
  declare function buildAndCtsQueryExpression(queryExpressions: Array<string>): string | null;
13
+ /**
14
+ * Compile one CTS query expression per OCR search term, in word order
15
+ *
16
+ * Highlighting matches each OCR word against these same per-term expressions,
17
+ * so hit locations always agree with what the filter matched.
18
+ * @internal
19
+ */
20
+ declare function buildOcrTermQueryExpressions(parameters: {
21
+ value: string;
22
+ matchMode: "includes" | "exact";
23
+ isCaseSensitive: boolean;
24
+ }): Array<string>;
25
+ /**
26
+ * Whether a query tree nests an OCR leaf inside an OR group
27
+ * @internal
28
+ */
29
+ declare function hasOcrQueryInDisjunction(query: Query, isInDisjunction?: boolean): boolean;
4
30
  declare function buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids: Array<string>, belongsToCollectionPropertyVariableUuid: string): string | null;
5
31
  declare function buildQueryPlan(parameters: {
6
32
  queries: Query | null;
7
33
  }): {
8
34
  prolog: string;
9
35
  queryExpression: string | null;
36
+ ocrBindings: Array<OcrUuidBinding>;
37
+ itemPredicates: string;
10
38
  };
11
39
  //#endregion
12
- export { buildAndCtsQueryExpression, buildBelongsToCollectionQueryExpression, buildQueryPlan };
40
+ export { OCR_DISJUNCTION_ERROR_MESSAGE, buildAndCtsQueryExpression, buildBelongsToCollectionQueryExpression, buildOcrTermQueryExpressions, buildQueryPlan, hasOcrQueryInDisjunction };
package/dist/query.mjs CHANGED
@@ -12,6 +12,11 @@ const CTS_INCLUDES_STOP_WORDS = /* @__PURE__ */ new Set([
12
12
  const CTS_INCLUDES_TOKEN_WORD_REGEX = /^\p{L}+$/u;
13
13
  const CTS_INCLUDES_TOKEN_REGEX = /[\p{L}\p{N}*?]+/gu;
14
14
  const CTS_EXACT_TEXT_TOKEN_REGEX = /[\p{L}\p{N}]+/gu;
15
+ /**
16
+ * Error message for OCR queries nested inside an OR group
17
+ * @internal
18
+ */
19
+ const OCR_DISJUNCTION_ERROR_MESSAGE = "OCR queries cannot be nested inside an OR group because they are resolved by a document join instead of a CTS query";
15
20
  const CONTENT_TARGET_CONTENT_ELEMENT_PATHS = {
16
21
  title: [
17
22
  "identification",
@@ -199,6 +204,9 @@ function buildNestedElementQuery(elementNames, queryExpression) {
199
204
  function buildNotCtsQueryExpression(queryExpression) {
200
205
  return `cts:not-query(${queryExpression})`;
201
206
  }
207
+ function buildCtsNearQueryExpression(queryExpressions) {
208
+ return `cts:near-query((${queryExpressions.join(", ")}), ${queryExpressions.length - 1}, ("ordered"))`;
209
+ }
202
210
  function buildAndCtsQueryExpressionInternal(queryExpressions) {
203
211
  if (queryExpressions.length === 0) return "cts:true-query()";
204
212
  if (queryExpressions.length === 1) return queryExpressions[0] ?? "cts:true-query()";
@@ -472,6 +480,63 @@ function buildItemStringQueryExpression(parameters) {
472
480
  language
473
481
  })]);
474
482
  }
483
+ function tokenizeOcrSearchValue(parameters) {
484
+ const { value, matchMode, isCaseSensitive } = parameters;
485
+ const rawTerms = (isCaseSensitive ? value : value.toLowerCase()).match(matchMode === "exact" ? CTS_EXACT_TEXT_TOKEN_REGEX : CTS_INCLUDES_TOKEN_REGEX) ?? [];
486
+ const terms = [];
487
+ for (const term of rawTerms) if (getWildcardStrippedValue(term) !== "") terms.push(term);
488
+ return terms;
489
+ }
490
+ /**
491
+ * Compile one CTS query expression per OCR search term, in word order
492
+ *
493
+ * Highlighting matches each OCR word against these same per-term expressions,
494
+ * so hit locations always agree with what the filter matched.
495
+ * @internal
496
+ */
497
+ function buildOcrTermQueryExpressions(parameters) {
498
+ const { value, matchMode, isCaseSensitive } = parameters;
499
+ const terms = tokenizeOcrSearchValue({
500
+ value,
501
+ matchMode,
502
+ isCaseSensitive
503
+ });
504
+ const isWholeWordEquality = matchMode === "exact" && terms.length === 1;
505
+ return Array.from(terms, (term) => isWholeWordEquality ? buildCtsElementValueQueryExpression({
506
+ elementName: "string",
507
+ value: term,
508
+ isCaseSensitive
509
+ }) : buildCtsWordQueryExpression({
510
+ value: term,
511
+ matchMode,
512
+ isCaseSensitive,
513
+ queryFamily: "text"
514
+ }));
515
+ }
516
+ function buildOcrQueryExpression(query) {
517
+ const termQueryExpressions = buildOcrTermQueryExpressions(query);
518
+ if (termQueryExpressions.length === 0) return "cts:false-query()";
519
+ return buildNestedElementQuery(["ocr"], termQueryExpressions.length > 1 ? buildCtsNearQueryExpression(termQueryExpressions) : termQueryExpressions[0] ?? "cts:false-query()");
520
+ }
521
+ function registerOcrItemPredicate(context, query) {
522
+ const bindingKey = [
523
+ query.value,
524
+ query.matchMode,
525
+ query.isCaseSensitive ? "case-sensitive" : "case-insensitive"
526
+ ].join("|");
527
+ let variableName = context.ocrVariableNamesByKey.get(bindingKey);
528
+ if (variableName == null) {
529
+ variableName = `$ocrUuids${context.nextOcrSerial}`;
530
+ context.nextOcrSerial += 1;
531
+ context.ocrVariableNamesByKey.set(bindingKey, variableName);
532
+ context.ocrBindings.push({
533
+ name: variableName,
534
+ expression: `for $ocrDocument in cts:search(doc(), ${buildOcrQueryExpression(query)})\n return document-uri($ocrDocument)`
535
+ });
536
+ }
537
+ const itemPredicate = query.isNegated === true ? `[not(@uuid = ${variableName})]` : `[@uuid = ${variableName}]`;
538
+ if (!context.itemPredicates.includes(itemPredicate)) context.itemPredicates.push(itemPredicate);
539
+ }
475
540
  function getLeafSearchValue(query) {
476
541
  switch (query.target) {
477
542
  case "string":
@@ -552,7 +617,11 @@ function createQueryCompilerContext() {
552
617
  return {
553
618
  nextHelperSerial: 1,
554
619
  helperNamesByKey: /* @__PURE__ */ new Map(),
555
- helperDeclarations: []
620
+ helperDeclarations: [],
621
+ nextOcrSerial: 1,
622
+ ocrVariableNamesByKey: /* @__PURE__ */ new Map(),
623
+ ocrBindings: [],
624
+ itemPredicates: []
556
625
  };
557
626
  }
558
627
  function registerConstantHelper(parameters) {
@@ -749,11 +818,24 @@ function getQueryGroupChildren(query) {
749
818
  function getQueryGroupOperator(query) {
750
819
  return "and" in query ? "and" : "or";
751
820
  }
821
+ function isDisjunctiveQueryGroup(query) {
822
+ return "or" in query && query.or.length > 1;
823
+ }
824
+ /**
825
+ * Whether a query tree nests an OCR leaf inside an OR group
826
+ * @internal
827
+ */
828
+ function hasOcrQueryInDisjunction(query, isInDisjunction = false) {
829
+ if (isQueryLeaf(query)) return query.target === "ocr" && isInDisjunction;
830
+ const isChildInDisjunction = isInDisjunction || isDisjunctiveQueryGroup(query);
831
+ for (const childQuery of getQueryGroupChildren(query)) if (hasOcrQueryInDisjunction(childQuery, isChildInDisjunction)) return true;
832
+ return false;
833
+ }
752
834
  function getCompatibleIncludesGroupLeaves(query) {
753
835
  if (!("or" in query) || query.or.length <= 1) return null;
754
836
  const leafQueries = [];
755
837
  for (const childQuery of query.or) {
756
- if (!isQueryLeaf(childQuery)) return null;
838
+ if (!isQueryLeaf(childQuery) || childQuery.target === "ocr") return null;
757
839
  leafQueries.push(childQuery);
758
840
  }
759
841
  const firstQuery = leafQueries[0];
@@ -814,14 +896,20 @@ function buildIncludesGroupQueryExpression(context, queries) {
814
896
  bodyExpression: buildOrCtsQueryExpressionInternal(exactMemberHelpers.map((helper) => helper.callExpression))
815
897
  }).callExpression, tokenizedQueryExpression]);
816
898
  }
817
- function buildQueryNode(context, query) {
899
+ function buildQueryNode(context, query, isInDisjunction) {
818
900
  if (isQueryLeaf(query)) {
901
+ if (query.target === "ocr") {
902
+ if (isInDisjunction) throw new Error(OCR_DISJUNCTION_ERROR_MESSAGE, { cause: query });
903
+ registerOcrItemPredicate(context, query);
904
+ return "cts:true-query()";
905
+ }
819
906
  const queryExpression = buildLeafQueryExpression(context, query);
820
907
  return query.isNegated === true ? buildNotCtsQueryExpression(queryExpression) : queryExpression;
821
908
  }
822
909
  const optimizedIncludesGroupQueries = getCompatibleIncludesGroupLeaves(query);
823
910
  if (optimizedIncludesGroupQueries != null) return buildIncludesGroupQueryExpression(context, optimizedIncludesGroupQueries);
824
- const childQueryExpressions = Array.from(getQueryGroupChildren(query), (childQuery) => buildQueryNode(context, childQuery));
911
+ const isChildInDisjunction = isInDisjunction || isDisjunctiveQueryGroup(query);
912
+ const childQueryExpressions = Array.from(getQueryGroupChildren(query), (childQuery) => buildQueryNode(context, childQuery, isChildInDisjunction));
825
913
  return (getQueryGroupOperator(query) === "and" ? buildAndCtsQueryExpressionInternal : buildOrCtsQueryExpressionInternal)(childQueryExpressions);
826
914
  }
827
915
  function buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, belongsToCollectionPropertyVariableUuid) {
@@ -839,14 +927,18 @@ function buildQueryPlan(parameters) {
839
927
  const { queries } = parameters;
840
928
  if (queries == null) return {
841
929
  prolog: "",
842
- queryExpression: null
930
+ queryExpression: null,
931
+ ocrBindings: [],
932
+ itemPredicates: ""
843
933
  };
844
934
  const context = createQueryCompilerContext();
845
- const queryExpression = buildQueryNode(context, queries);
935
+ const queryExpression = buildQueryNode(context, queries, false);
846
936
  return {
847
937
  prolog: context.helperDeclarations.join("\n\n"),
848
- queryExpression
938
+ queryExpression,
939
+ ocrBindings: context.ocrBindings,
940
+ itemPredicates: context.itemPredicates.join("")
849
941
  };
850
942
  }
851
943
  //#endregion
852
- export { buildAndCtsQueryExpression, buildBelongsToCollectionQueryExpression, buildQueryPlan };
944
+ export { OCR_DISJUNCTION_ERROR_MESSAGE, buildAndCtsQueryExpression, buildBelongsToCollectionQueryExpression, buildOcrTermQueryExpressions, buildQueryPlan, hasOcrQueryInDisjunction };
@@ -38,7 +38,7 @@ declare const renderOptionsSchema: v.SchemaWithPipe<readonly [v.StringSchema<und
38
38
  declare const setPropertyValuesParametersSchema: v.ObjectSchema<{
39
39
  readonly setScopeUuids: v.SchemaWithPipe<readonly [v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.CheckAction<string, "Invalid pseudo-UUID">]>, undefined>, v.MinLengthAction<string[], 1, "At least one set scope UUID is required">]>;
40
40
  readonly belongsToCollectionScopeUuids: v.OptionalSchema<v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.CheckAction<string, "Invalid pseudo-UUID">]>, undefined>, readonly []>;
41
- readonly queries: v.OptionalSchema<v.NullableSchema<v.GenericSchema<unknown, Query>, undefined>, null>;
41
+ readonly queries: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.GenericSchema<unknown, Query>, v.CheckAction<Query, "OCR queries cannot be nested inside an OR group because they are resolved by a document join instead of a CTS query">]>, undefined>, null>;
42
42
  readonly attributes: v.OptionalSchema<v.ObjectSchema<{
43
43
  readonly bibliographies: v.GenericSchema<unknown, boolean>;
44
44
  readonly periods: v.GenericSchema<unknown, boolean>;
@@ -48,6 +48,17 @@ declare const setPropertyValuesParametersSchema: v.ObjectSchema<{
48
48
  }>;
49
49
  readonly isLimitedToLeafPropertyValues: v.GenericSchema<unknown, boolean>;
50
50
  }, undefined>;
51
+ /**
52
+ * Schema for validating OCR matches parameters
53
+ * @internal
54
+ */
55
+ declare const ocrMatchesParametersSchema: v.ObjectSchema<{
56
+ readonly uuids: v.SchemaWithPipe<readonly [v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.CheckAction<string, "Invalid pseudo-UUID">]>, undefined>, v.MinLengthAction<string[], 1, "At least one UUID is required">]>;
57
+ readonly value: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, "A search value is required">]>;
58
+ readonly matchMode: v.OptionalSchema<v.PicklistSchema<["includes", "exact"], undefined>, "includes">;
59
+ readonly isCaseSensitive: v.GenericSchema<unknown, boolean>;
60
+ readonly maxMatchesPerItem: v.OptionalSchema<v.GenericSchema<unknown, number>, 50>;
61
+ }, undefined>;
51
62
  /**
52
63
  * Schema for validating Set items parameters
53
64
  * @internal
@@ -55,7 +66,7 @@ declare const setPropertyValuesParametersSchema: v.ObjectSchema<{
55
66
  declare const setItemsParametersSchema: v.ObjectSchema<{
56
67
  readonly setScopeUuids: v.SchemaWithPipe<readonly [v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.CheckAction<string, "Invalid pseudo-UUID">]>, undefined>, v.MinLengthAction<string[], 1, "At least one set scope UUID is required">]>;
57
68
  readonly belongsToCollectionScopeUuids: v.OptionalSchema<v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.CheckAction<string, "Invalid pseudo-UUID">]>, undefined>, readonly []>;
58
- readonly queries: v.OptionalSchema<v.NullableSchema<v.GenericSchema<unknown, Query>, undefined>, null>;
69
+ readonly queries: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.GenericSchema<unknown, Query>, v.CheckAction<Query, "OCR queries cannot be nested inside an OR group because they are resolved by a document join instead of a CTS query">]>, undefined>, null>;
59
70
  readonly sort: v.OptionalSchema<v.VariantSchema<"target", [v.StrictObjectSchema<{
60
71
  readonly target: v.LiteralSchema<"none", undefined>;
61
72
  }, undefined>, v.StrictObjectSchema<{
@@ -75,4 +86,4 @@ declare const setItemsParametersSchema: v.ObjectSchema<{
75
86
  readonly pageSize: v.OptionalSchema<v.GenericSchema<unknown, number>, 48>;
76
87
  }, undefined>;
77
88
  //#endregion
78
- export { componentSchema, gallerySchema, iso639_3Schema, renderOptionsSchema, setItemsParametersSchema, setPropertyValuesParametersSchema, uuidSchema };
89
+ export { componentSchema, gallerySchema, iso639_3Schema, ocrMatchesParametersSchema, renderOptionsSchema, setItemsParametersSchema, setPropertyValuesParametersSchema, uuidSchema };
package/dist/schemas.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { isPseudoUuid } from "./utilities.mjs";
2
2
  import "./helpers.mjs";
3
+ import { OCR_DISJUNCTION_ERROR_MESSAGE, hasOcrQueryInDisjunction } from "./query.mjs";
3
4
  import * as v from "valibot";
4
5
  //#region src/schemas.ts
5
6
  const positiveNumber = (message) => v.pipe(v.number(), v.minValue(1, message));
@@ -141,6 +142,13 @@ const setQueryLeafSchema = v.union([
141
142
  value: v.string(),
142
143
  ...standardQueryFields
143
144
  }),
145
+ v.strictObject({
146
+ target: v.literal("ocr"),
147
+ value: v.string(),
148
+ matchMode: standardQueryFields.matchMode,
149
+ isCaseSensitive: standardQueryFields.isCaseSensitive,
150
+ isNegated: standardQueryFields.isNegated
151
+ }),
144
152
  v.strictObject({
145
153
  target: v.picklist([
146
154
  "title",
@@ -167,7 +175,7 @@ const setQuerySchema = v.lazy(() => v.union([
167
175
  * Schema for validating Set queries
168
176
  * @internal
169
177
  */
170
- const setQueriesSchema = v.optional(v.nullable(setQuerySchema), null);
178
+ const setQueriesSchema = v.optional(v.nullable(v.pipe(setQuerySchema, v.check((query) => !hasOcrQueryInDisjunction(query), OCR_DISJUNCTION_ERROR_MESSAGE))), null);
171
179
  /**
172
180
  * Schema for validating Set items sort
173
181
  * @internal
@@ -214,6 +222,17 @@ const setPropertyValuesParametersSchema = v.object({
214
222
  isLimitedToLeafPropertyValues: defaultBoolean(false)
215
223
  });
216
224
  /**
225
+ * Schema for validating OCR matches parameters
226
+ * @internal
227
+ */
228
+ const ocrMatchesParametersSchema = v.object({
229
+ uuids: v.pipe(v.array(uuidSchema), v.minLength(1, "At least one UUID is required")),
230
+ value: v.pipe(v.string(), v.minLength(1, "A search value is required")),
231
+ matchMode: v.optional(v.picklist(["includes", "exact"]), "includes"),
232
+ isCaseSensitive: defaultBoolean(false),
233
+ maxMatchesPerItem: v.optional(positiveNumber("Max matches per item must be positive"), 50)
234
+ });
235
+ /**
217
236
  * Schema for validating Set items parameters
218
237
  * @internal
219
238
  */
@@ -226,4 +245,4 @@ const setItemsParametersSchema = v.object({
226
245
  pageSize: v.optional(positiveNumber("Page size must be positive"), 48)
227
246
  });
228
247
  //#endregion
229
- export { componentSchema, gallerySchema, iso639_3Schema, renderOptionsSchema, setItemsParametersSchema, setPropertyValuesParametersSchema, uuidSchema };
248
+ export { componentSchema, gallerySchema, iso639_3Schema, ocrMatchesParametersSchema, renderOptionsSchema, setItemsParametersSchema, setPropertyValuesParametersSchema, uuidSchema };
@@ -262,6 +262,60 @@ type ImageMap = {
262
262
  width: number;
263
263
  height: number;
264
264
  };
265
+ /**
266
+ * Vertex of an OCR word's bounding polygon in OCHRE
267
+ */
268
+ type OcrPoint = {
269
+ x: number;
270
+ y: number;
271
+ };
272
+ /**
273
+ * Word recognized within an OCR text line in OCHRE
274
+ */
275
+ type OcrWord = {
276
+ content: string;
277
+ x: number;
278
+ y: number;
279
+ width: number;
280
+ height: number;
281
+ vertices: Array<OcrPoint>;
282
+ };
283
+ /**
284
+ * Text line of an OCR text block in OCHRE
285
+ */
286
+ type OcrTextLine = {
287
+ content: string;
288
+ words: Array<OcrWord>;
289
+ };
290
+ /**
291
+ * Text block of an OCR page in OCHRE
292
+ */
293
+ type OcrTextBlock = {
294
+ lines: Array<OcrTextLine>;
295
+ };
296
+ /**
297
+ * OCR page of a resource in OCHRE
298
+ */
299
+ type OcrPage = {
300
+ number: number | null;
301
+ fileName: string | null;
302
+ width: number | null;
303
+ height: number | null;
304
+ blocks: Array<OcrTextBlock>;
305
+ };
306
+ /**
307
+ * A run of adjacent OCR words matching a search value in OCHRE
308
+ *
309
+ * `uuid` is the requested resource, while `resourceUuid` is the resource that
310
+ * owns the OCR page — they differ when the OCR lives on a child page resource.
311
+ */
312
+ type OcrMatch = {
313
+ uuid: string;
314
+ resourceUuid: string | null;
315
+ page: Omit<OcrPage, "blocks">;
316
+ content: string;
317
+ words: Array<OcrWord>;
318
+ };
265
319
  /**
266
320
  * Note in OCHRE
267
321
  */
@@ -680,6 +734,7 @@ type Resource<T extends LanguageCodes = LanguageCodes, U extends ItemPayloadKind
680
734
  image: Image<T> | null;
681
735
  document: MultilingualString<T> | null;
682
736
  imageMap: ImageMap | null;
737
+ ocr: Array<OcrPage>;
683
738
  coordinates: Array<Coordinates<T>>;
684
739
  periods: Array<Period<T, "embedded">>;
685
740
  links: ItemLinks<T>;
@@ -789,6 +844,13 @@ type SetItemsSort = {
789
844
  };
790
845
  /**
791
846
  * Represents a leaf query for Set items
847
+ *
848
+ * The `ocr` target matches the OCR text layer of resources. Because OCR is not
849
+ * carried by Set item projections, it is resolved by a document join rather
850
+ * than a CTS term, which means `ocr` leaves compose with `and` and `isNegated`
851
+ * but cannot be nested inside an `or` group. Both match modes are adjacency
852
+ * based: `includes` allows stemming and wildcards, `exact` requires whole OCR
853
+ * words. OCR carries no language, so `ocr` leaves take no `language`.
792
854
  */
793
855
  type QueryLeaf = {
794
856
  target: "property";
@@ -855,6 +917,12 @@ type QueryLeaf = {
855
917
  isCaseSensitive: boolean;
856
918
  language: string;
857
919
  isNegated?: boolean;
920
+ } | {
921
+ target: "ocr";
922
+ value: string;
923
+ matchMode: "includes" | "exact";
924
+ isCaseSensitive: boolean;
925
+ isNegated?: boolean;
858
926
  } | {
859
927
  target: "title" | "description" | "image" | "periods" | "bibliography" | "notes";
860
928
  value: string;
@@ -876,4 +944,4 @@ type QueryGroup = {
876
944
  */
877
945
  type Query = QueryLeaf | QueryGroup;
878
946
  //#endregion
879
- export { AnyBibliography, AnyConcept, AnyItem, AnyPeriod, AnyPerson, AnyPropertyValue, AnyPropertyVariable, AnyResource, AnySet, AnySpatialUnit, AnyText, AnyTree, BaseItem, BaseItemLink, BelongsTo, Bibliography, BibliographyEntryInfo, BibliographyItemLink, BibliographySourceDocument, Concept, ConceptItemLink, ContainedItemCategory, ContainedItemCategoryFromOption, ContainedItemCategoryOption, Context, ContextItem, ContextItemCategory, ContextNode, Coordinates, CoordinatesSource, DictionaryUnitItemLink, EmbeddedBibliography, EmbeddedConcept, EmbeddedItem, EmbeddedPeriod, EmbeddedPerson, EmbeddedPropertyValue, EmbeddedPropertyVariable, EmbeddedResource, EmbeddedSet, EmbeddedSpatialUnit, EmbeddedText, EmbeddedTree, Event, Gallery, Heading, HeadingItemCategory, Identification, Image, ImageMap, ImageMapArea, Interpretation, Item, ItemCategory, ItemCategoryFromOption, ItemCategoryOption, ItemCategoryWithEmbeddedItems, ItemContainerCategory, ItemLink, ItemLinkCategory, ItemLinks, ItemPayloadKind, ItemProperty, ItemWithoutEmbeddedItems, LanguageCodes, License, Metadata, Note, Observation, Period, PeriodItemLink, Person, PersonItemLink, Prettify, Property, PropertyLike, PropertyRelation, PropertyValue, PropertyValueContent, PropertyValueDataType, PropertyValueItemLink, PropertyValueQueryItem, PropertyVariable, PropertyVariableItemLink, Query, QueryGroup, QueryLeaf, QueryablePropertyValueDataType, RecursiveItemCategory, Resource, ResourceItemLink, Section, Set, SetAttributeValueQueryItem, SetBibliography, SetConcept, SetItem, SetItemCategory, SetItemLink, SetItemProperty, SetItemSimplifiedProperty, SetItemsSort, SetItemsSortDirection, SetPeriod, SetResource, SetSpatialUnit, SetTree, SimplifiedProperty, SpatialUnit, SpatialUnitItemLink, Text, TextItemLink, TopLevelItem, Tree, TreeItemCategory, TreeItemLink };
947
+ export { AnyBibliography, AnyConcept, AnyItem, AnyPeriod, AnyPerson, AnyPropertyValue, AnyPropertyVariable, AnyResource, AnySet, AnySpatialUnit, AnyText, AnyTree, BaseItem, BaseItemLink, BelongsTo, Bibliography, BibliographyEntryInfo, BibliographyItemLink, BibliographySourceDocument, Concept, ConceptItemLink, ContainedItemCategory, ContainedItemCategoryFromOption, ContainedItemCategoryOption, Context, ContextItem, ContextItemCategory, ContextNode, Coordinates, CoordinatesSource, DictionaryUnitItemLink, EmbeddedBibliography, EmbeddedConcept, EmbeddedItem, EmbeddedPeriod, EmbeddedPerson, EmbeddedPropertyValue, EmbeddedPropertyVariable, EmbeddedResource, EmbeddedSet, EmbeddedSpatialUnit, EmbeddedText, EmbeddedTree, Event, Gallery, Heading, HeadingItemCategory, Identification, Image, ImageMap, ImageMapArea, Interpretation, Item, ItemCategory, ItemCategoryFromOption, ItemCategoryOption, ItemCategoryWithEmbeddedItems, ItemContainerCategory, ItemLink, ItemLinkCategory, ItemLinks, ItemPayloadKind, ItemProperty, ItemWithoutEmbeddedItems, LanguageCodes, License, Metadata, Note, Observation, OcrMatch, OcrPage, OcrPoint, OcrTextBlock, OcrTextLine, OcrWord, Period, PeriodItemLink, Person, PersonItemLink, Prettify, Property, PropertyLike, PropertyRelation, PropertyValue, PropertyValueContent, PropertyValueDataType, PropertyValueItemLink, PropertyValueQueryItem, PropertyVariable, PropertyVariableItemLink, Query, QueryGroup, QueryLeaf, QueryablePropertyValueDataType, RecursiveItemCategory, Resource, ResourceItemLink, Section, Set, SetAttributeValueQueryItem, SetBibliography, SetConcept, SetItem, SetItemCategory, SetItemLink, SetItemProperty, SetItemSimplifiedProperty, SetItemsSort, SetItemsSortDirection, SetPeriod, SetResource, SetSpatialUnit, SetTree, SimplifiedProperty, SpatialUnit, SpatialUnitItemLink, Text, TextItemLink, TopLevelItem, Tree, TreeItemCategory, TreeItemLink };
@@ -1,6 +1,11 @@
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";
1
+ import { XMLData as XMLData$1, XMLDataItem as XMLDataItem$1, XMLGalleryData as XMLGalleryData$1, XMLItemLinksData as XMLItemLinksData$1, XMLLink as XMLLink$1, XMLOcrMatchesData as XMLOcrMatchesData$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>;
4
9
  declare const XMLLink: v.GenericSchema<unknown, XMLLink$1>;
5
10
  declare const XMLDataItem: v.GenericSchema<unknown, XMLDataItem$1>;
6
11
  declare const XMLItemLinksData: v.GenericSchema<unknown, XMLItemLinksData$1>;
@@ -9,4 +14,4 @@ declare const XMLSetItemsData: v.GenericSchema<unknown, XMLSetItemsData$1>;
9
14
  declare const XMLData: v.GenericSchema<unknown, XMLData$1>;
10
15
  declare const XMLWebsiteData: v.GenericSchema<unknown, XMLWebsiteData$1>;
11
16
  //#endregion
12
- export { XMLData, XMLDataItem, XMLGalleryData, XMLItemLinksData, XMLLink, XMLSetItemsData, XMLWebsiteData };
17
+ export { XMLData, XMLDataItem, XMLGalleryData, XMLItemLinksData, XMLLink, XMLOcrMatchesData, XMLSetItemsData, XMLWebsiteData };
@@ -220,6 +220,42 @@ const XMLImageMap = v.object({
220
220
  width: XMLNumber,
221
221
  height: XMLNumber
222
222
  }, "XMLImageMap: Shape error");
223
+ const XMLOcrString = v.object({
224
+ HPOS: XMLNumber,
225
+ VPOS: XMLNumber,
226
+ WIDTH: XMLNumber,
227
+ HEIGHT: XMLNumber,
228
+ CONTENT: v.string("XMLOcrString: CONTENT is string and required"),
229
+ VERTICES: v.optional(v.string("XMLOcrString: VERTICES is string and optional"))
230
+ }, "XMLOcrString: Shape error");
231
+ const XMLOcrTextLine = v.object({ string: v.optional(v.array(XMLOcrString, "XMLOcrTextLine: string is array of XMLOcrString")) }, "XMLOcrTextLine: Shape error");
232
+ const XMLOcrTextBlock = v.object({ TextLine: v.optional(v.array(XMLOcrTextLine, "XMLOcrTextBlock: TextLine is array of XMLOcrTextLine")) }, "XMLOcrTextBlock: Shape error");
233
+ const XMLOcrPage = v.object({
234
+ n: XMLOptionalNumber,
235
+ fileName: v.optional(v.string("XMLOcrPage: fileName is string and optional")),
236
+ WIDTH: XMLOptionalNumber,
237
+ HEIGHT: XMLOptionalNumber,
238
+ TextBlock: v.optional(v.array(XMLOcrTextBlock, "XMLOcrPage: TextBlock is array of XMLOcrTextBlock"))
239
+ }, "XMLOcrPage: Shape error");
240
+ const XMLOcr = v.object({ Page: v.optional(v.array(XMLOcrPage, "XMLOcr: Page is array of XMLOcrPage")) }, "XMLOcr: Shape error");
241
+ const XMLOcrMatch = v.object({
242
+ resourceUuid: v.optional(v.string("XMLOcrMatch: resourceUuid is string and optional")),
243
+ n: XMLOptionalNumber,
244
+ fileName: v.optional(v.string("XMLOcrMatch: fileName is string and optional")),
245
+ WIDTH: XMLOptionalNumber,
246
+ HEIGHT: XMLOptionalNumber,
247
+ string: v.optional(v.array(XMLOcrString, "XMLOcrMatch: string is array of XMLOcrString"))
248
+ }, "XMLOcrMatch: Shape error");
249
+ const XMLOcrMatchItem = v.object({
250
+ uuid: v.pipe(v.string("XMLOcrMatchItem: uuid is string and required"), v.check(isPseudoUuid, "XMLOcrMatchItem: uuid is not a valid pseudo-UUID")),
251
+ matchCount: XMLNumber,
252
+ ocrMatch: v.optional(v.array(XMLOcrMatch, "XMLOcrMatchItem: ocrMatch is array of XMLOcrMatch"))
253
+ }, "XMLOcrMatchItem: Shape error");
254
+ /**
255
+ * Schema for validating OCR matches fetched from the OCHRE API
256
+ * @internal
257
+ */
258
+ 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");
223
259
  const XMLNote = v.object({
224
260
  content: v.optional(XMLContent.entries.content),
225
261
  payload: v.optional(v.string("XMLNote: payload is string and optional")),
@@ -640,6 +676,7 @@ const XMLResource = v.object({
640
676
  width: XMLOptionalNumber,
641
677
  image: v.optional(XMLImage),
642
678
  imagemap: v.optional(XMLImageMap),
679
+ ocr: v.optional(XMLOcr),
643
680
  document: v.optional(XMLContent),
644
681
  coordinates: v.optional(XMLCoordinates),
645
682
  periods: v.optional(v.object({ period: v.array(XMLPeriod) })),
@@ -791,6 +828,7 @@ const XMLWebsiteResource = v.lazy(() => v.object({
791
828
  width: XMLOptionalNumber,
792
829
  image: v.optional(XMLImage),
793
830
  imagemap: v.optional(XMLImageMap),
831
+ ocr: v.optional(XMLOcr),
794
832
  document: v.optional(XMLContent),
795
833
  coordinates: v.optional(XMLCoordinates),
796
834
  periods: v.optional(v.object({ period: v.array(XMLPeriod) })),
@@ -924,4 +962,4 @@ const XMLWebsiteData = v.object({ result: v.object({ ochre: v.object({
924
962
  tree: v.array(XMLWebsiteTree)
925
963
  }, "XMLWebsiteData: ochre is object with website tree") }, "XMLWebsiteData: result is object with ochre") }, "XMLWebsiteData: Shape error");
926
964
  //#endregion
927
- export { XMLData, XMLDataItem, XMLGalleryData, XMLItemLinksData, XMLLink, XMLSetItemsData, XMLWebsiteData };
965
+ export { XMLData, XMLDataItem, XMLGalleryData, XMLItemLinksData, XMLLink, XMLOcrMatchesData, XMLSetItemsData, XMLWebsiteData };
@@ -175,6 +175,48 @@ type XMLImageMap = {
175
175
  width: XMLNumber;
176
176
  height: XMLNumber;
177
177
  };
178
+ type XMLOcrString = {
179
+ HPOS: XMLNumber;
180
+ VPOS: XMLNumber;
181
+ WIDTH: XMLNumber;
182
+ HEIGHT: XMLNumber;
183
+ CONTENT: string;
184
+ VERTICES?: string;
185
+ };
186
+ type XMLOcrTextLine = {
187
+ string?: Array<XMLOcrString>;
188
+ };
189
+ type XMLOcrTextBlock = {
190
+ TextLine?: Array<XMLOcrTextLine>;
191
+ };
192
+ type XMLOcrPage = {
193
+ n?: XMLNumber;
194
+ fileName?: string;
195
+ WIDTH?: XMLNumber;
196
+ HEIGHT?: XMLNumber;
197
+ TextBlock?: Array<XMLOcrTextBlock>;
198
+ };
199
+ type XMLOcr = {
200
+ Page?: Array<XMLOcrPage>;
201
+ };
202
+ type XMLOcrMatch = Omit<XMLOcrPage, "TextBlock"> & {
203
+ resourceUuid?: string;
204
+ string?: Array<XMLOcrString>;
205
+ };
206
+ type XMLOcrMatchItem = {
207
+ uuid: string;
208
+ matchCount: XMLNumber;
209
+ ocrMatch?: Array<XMLOcrMatch>;
210
+ };
211
+ type XMLOcrMatchesData = {
212
+ result: {
213
+ ochre: {
214
+ ocrMatches?: {
215
+ ocrItem?: Array<XMLOcrMatchItem>;
216
+ };
217
+ };
218
+ };
219
+ };
178
220
  type XMLNote = Partial<XMLContent> & XMLString & {
179
221
  noteNo?: XMLNumber;
180
222
  title?: string;
@@ -556,6 +598,7 @@ type XMLResource = XMLBaseItem & {
556
598
  width?: XMLNumber;
557
599
  image?: XMLImage;
558
600
  imagemap?: XMLImageMap;
601
+ ocr?: XMLOcr;
559
602
  document?: XMLContent;
560
603
  coordinates?: XMLCoordinates;
561
604
  periods?: {
@@ -913,4 +956,4 @@ type XMLWebsiteData = {
913
956
  };
914
957
  };
915
958
  //#endregion
916
- 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, XMLSection, XMLSet, XMLSetItems, XMLSetItemsData, XMLSimplifiedProperty, XMLSpatialUnit, XMLString, XMLText, XMLTree, XMLWebsiteContext, XMLWebsiteContextItem, XMLWebsiteContextLevel, XMLWebsiteData, XMLWebsiteFilterContext, XMLWebsiteFilterContextItem, XMLWebsiteOptions, XMLWebsiteProperties, XMLWebsiteResource, XMLWebsiteResourceGroup, XMLWebsiteResourceItem, XMLWebsiteScope, XMLWebsiteSegment, XMLWebsiteStyle, XMLWebsiteTree };
959
+ 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, XMLOcr, XMLOcrMatch, XMLOcrMatchItem, XMLOcrMatchesData, XMLOcrPage, XMLOcrString, XMLOcrTextBlock, XMLOcrTextLine, XMLPeriod, XMLPerson, XMLProperty, XMLPropertyRelation, XMLPropertyValue, XMLPropertyVariable, XMLRecursiveItemCategory, XMLResource, 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.69",
3
+ "version": "1.0.71",
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",
@@ -53,10 +53,10 @@
53
53
  "devDependencies": {
54
54
  "@antfu/eslint-config": "^9.2.0",
55
55
  "@types/node": "^24.13.3",
56
- "bumpp": "^12.0.0",
56
+ "bumpp": "^12.1.1",
57
57
  "eslint": "^10.8.0",
58
- "knip": "^6.29.0",
59
- "oxfmt": "^0.60.0",
58
+ "knip": "^6.31.0",
59
+ "oxfmt": "^0.61.0",
60
60
  "tsdown": "^0.22.14",
61
61
  "typescript": "^6.0.3",
62
62
  "vitest": "^4.1.10"