ochre-sdk 0.21.6 → 0.21.7

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/dist/index.d.mts CHANGED
@@ -670,15 +670,14 @@ type SetItemsSort = {
670
670
  direction?: SetItemsSortDirection;
671
671
  language?: string;
672
672
  };
673
- type QueryPropertyDataType = Exclude<PropertyValueContentType, "coordinate"> | "all";
674
673
  /**
675
674
  * Represents a leaf query for Set items
676
675
  */
677
676
  type QueryLeaf = {
678
677
  target: "property";
679
678
  propertyVariable?: string;
680
- dataType: "all";
681
- value: string;
679
+ dataType: Exclude<Exclude<PropertyValueContentType, "coordinate">, "date" | "dateTime">;
680
+ value?: string;
682
681
  from?: never;
683
682
  to?: never;
684
683
  matchMode: "includes" | "exact";
@@ -687,9 +686,9 @@ type QueryLeaf = {
687
686
  isNegated?: boolean;
688
687
  } | {
689
688
  target: "property";
690
- propertyVariable?: string;
691
- dataType: Exclude<PropertyValueContentType, "coordinate">;
692
- value?: string;
689
+ propertyVariable: string;
690
+ dataType: "date" | "dateTime";
691
+ value: string;
693
692
  from?: never;
694
693
  to?: never;
695
694
  matchMode: "includes" | "exact";
@@ -718,6 +717,15 @@ type QueryLeaf = {
718
717
  isCaseSensitive: boolean;
719
718
  language: string;
720
719
  isNegated?: boolean;
720
+ } | {
721
+ target: "property";
722
+ propertyVariable?: string;
723
+ dataType: "all";
724
+ value: string;
725
+ matchMode: "includes" | "exact";
726
+ isCaseSensitive: boolean;
727
+ language: string;
728
+ isNegated?: boolean;
721
729
  } | {
722
730
  target: "string";
723
731
  value: string;
@@ -1528,4 +1536,4 @@ declare const DEFAULT_PAGE_SIZE = 48;
1528
1536
  */
1529
1537
  declare function flattenItemProperties<T extends DataCategory = DataCategory, U extends DataCategory | Array<DataCategory> = (T extends "tree" ? Exclude<DataCategory, "tree"> : T extends "set" ? Array<DataCategory> : never)>(item: Item<T, U>): Item<T, U>;
1530
1538
  //#endregion
1531
- export { ApiVersion, Bibliography, Concept, Context, ContextItem, ContextNode, Coordinate, DEFAULT_API_VERSION, DEFAULT_PAGE_SIZE, Data, DataCategory, Event, FileFormat, Gallery, Identification, Image, ImageMap, ImageMapArea, Interpretation, Item, LevelContext, LevelContextItem, License, Link, Metadata, Note, Observation, Period, Person, Property, PropertyContexts, PropertyValue, PropertyValueContent, PropertyValueContentType, PropertyValueQueryItem, PropertyVariable, Query, QueryGroup, QueryLeaf, QueryPropertyDataType, Resource, Scope, Section, Set, SetAttributeValueQueryItem, SetItemsSort, SetItemsSortDirection, SpatialUnit, Style, StylesheetCategory, StylesheetItem, Text, Tree, WebBlock, WebBlockLayout, WebElement, WebElementComponent, WebImage, WebSegment, WebSegmentItem, WebTitle, Webpage, Website, WebsitePropertyQuery, WebsitePropertyQueryNode, WebsiteType, fetchGallery, fetchItem, fetchSetItems, fetchSetPropertyValues, fetchWebsite, filterProperties, flattenItemProperties, getLeafPropertyValues, getPropertyByLabel, getPropertyByLabelAndValue, getPropertyByLabelAndValues, getPropertyByUuid, getPropertyValueByLabel, getPropertyValueByUuid, getPropertyValuesByLabel, getPropertyValuesByUuid, getUniqueProperties, getUniquePropertyLabels };
1539
+ export { ApiVersion, Bibliography, Concept, Context, ContextItem, ContextNode, Coordinate, DEFAULT_API_VERSION, DEFAULT_PAGE_SIZE, Data, DataCategory, Event, FileFormat, Gallery, Identification, Image, ImageMap, ImageMapArea, Interpretation, Item, LevelContext, LevelContextItem, License, Link, Metadata, Note, Observation, Period, Person, Property, PropertyContexts, PropertyValue, PropertyValueContent, PropertyValueContentType, PropertyValueQueryItem, PropertyVariable, Query, QueryGroup, QueryLeaf, Resource, Scope, Section, Set, SetAttributeValueQueryItem, SetItemsSort, SetItemsSortDirection, SpatialUnit, Style, StylesheetCategory, StylesheetItem, Text, Tree, WebBlock, WebBlockLayout, WebElement, WebElementComponent, WebImage, WebSegment, WebSegmentItem, WebTitle, Webpage, Website, WebsitePropertyQuery, WebsitePropertyQueryNode, WebsiteType, fetchGallery, fetchItem, fetchSetItems, fetchSetPropertyValues, fetchWebsite, filterProperties, flattenItemProperties, getLeafPropertyValues, getPropertyByLabel, getPropertyByLabelAndValue, getPropertyByLabelAndValues, getPropertyByUuid, getPropertyValueByLabel, getPropertyValueByUuid, getPropertyValuesByLabel, getPropertyValuesByUuid, getUniqueProperties, getUniquePropertyLabels };
package/dist/index.mjs CHANGED
@@ -757,18 +757,6 @@ const boundsSchema = z.string().transform((str, ctx) => {
757
757
  * @internal
758
758
  */
759
759
  const setQueryLeafSchema = z.union([
760
- z.object({
761
- target: z.literal("property"),
762
- propertyVariable: uuidSchema.optional(),
763
- dataType: z.literal("all"),
764
- value: z.string(),
765
- from: z.never().optional(),
766
- to: z.never().optional(),
767
- matchMode: z.enum(["includes", "exact"]),
768
- isCaseSensitive: z.boolean(),
769
- language: z.string().default("eng"),
770
- isNegated: z.boolean().optional().default(false)
771
- }).strict(),
772
760
  z.object({
773
761
  target: z.literal("property"),
774
762
  propertyVariable: uuidSchema.optional(),
@@ -777,14 +765,10 @@ const setQueryLeafSchema = z.union([
777
765
  "integer",
778
766
  "decimal",
779
767
  "boolean",
780
- "IDREF",
781
- "date",
782
- "dateTime",
783
- "time"
768
+ "time",
769
+ "IDREF"
784
770
  ]),
785
771
  value: z.string().optional(),
786
- from: z.never().optional(),
787
- to: z.never().optional(),
788
772
  matchMode: z.enum(["includes", "exact"]),
789
773
  isCaseSensitive: z.boolean(),
790
774
  language: z.string().default("eng"),
@@ -795,6 +779,18 @@ const setQueryLeafSchema = z.union([
795
779
  message: "Property queries must include at least one propertyVariable or value"
796
780
  });
797
781
  }),
782
+ z.object({
783
+ target: z.literal("property"),
784
+ propertyVariable: uuidSchema,
785
+ dataType: z.enum(["date", "dateTime"]),
786
+ value: z.string(),
787
+ from: z.never().optional(),
788
+ to: z.never().optional(),
789
+ matchMode: z.enum(["includes", "exact"]),
790
+ isCaseSensitive: z.boolean(),
791
+ language: z.string().default("eng"),
792
+ isNegated: z.boolean().optional().default(false)
793
+ }).strict(),
798
794
  z.object({
799
795
  target: z.literal("property"),
800
796
  propertyVariable: uuidSchema,
@@ -819,6 +815,16 @@ const setQueryLeafSchema = z.union([
819
815
  language: z.string().default("eng"),
820
816
  isNegated: z.boolean().optional().default(false)
821
817
  }).strict(),
818
+ z.object({
819
+ target: z.literal("property"),
820
+ propertyVariable: uuidSchema.optional(),
821
+ dataType: z.literal("all"),
822
+ value: z.string(),
823
+ matchMode: z.enum(["includes", "exact"]),
824
+ isCaseSensitive: z.boolean(),
825
+ language: z.string().default("eng"),
826
+ isNegated: z.boolean().optional().default(false)
827
+ }).strict(),
822
828
  z.object({
823
829
  target: z.literal("string"),
824
830
  value: z.string(),
@@ -2162,28 +2168,6 @@ const CTS_INCLUDES_STOP_WORDS = [
2162
2168
  ];
2163
2169
  const CTS_INCLUDES_STOP_WORDS_VAR = "$ctsIncludesStopWords";
2164
2170
  const CTS_INCLUDES_TOKEN_SPLIT_REGEX = /\W+/u;
2165
- const CONTENT_TARGET_CONFIGS = {
2166
- title: {
2167
- containerElementName: "identification",
2168
- getContentNodesExpression: (language) => `identification/label/content[@xml:lang="${language}"]`
2169
- },
2170
- description: {
2171
- containerElementName: "description",
2172
- getContentNodesExpression: (language) => `description/content[@xml:lang="${language}"]`
2173
- },
2174
- image: {
2175
- containerElementName: "image",
2176
- getContentNodesExpression: (language) => `image/identification/label/content[@xml:lang="${language}"]`
2177
- },
2178
- periods: {
2179
- containerElementName: "period",
2180
- getContentNodesExpression: (language) => `periods/period/identification/label/content[@xml:lang="${language}"]`
2181
- },
2182
- bibliography: {
2183
- containerElementName: "bibliography",
2184
- getContentNodesExpression: (language) => `bibliographies/bibliography/identification/label/content[@xml:lang="${language}"]`
2185
- }
2186
- };
2187
2171
  function tokenizeIncludesSearchValue(params) {
2188
2172
  const { value, isCaseSensitive } = params;
2189
2173
  const rawTerms = (isCaseSensitive ? value : value.toLowerCase()).split(CTS_INCLUDES_TOKEN_SPLIT_REGEX);
@@ -2198,10 +2182,6 @@ function buildFlattenedContentValuesExpression(contentNodesExpression) {
2198
2182
  return `for $content in ${contentNodesExpression}
2199
2183
  return string-join($content//text(), "")`;
2200
2184
  }
2201
- function buildNodeStringValuesExpression(nodesExpression) {
2202
- return `for $node in ${nodesExpression}
2203
- return string($node)`;
2204
- }
2205
2185
  function buildSearchableContentNodesExpression(contentNodesExpression) {
2206
2186
  return `for $content in ${contentNodesExpression}
2207
2187
  return (
@@ -2216,17 +2196,46 @@ function buildCombinedSearchableContentNodesExpression(contentNodesExpressions)
2216
2196
  if (searchableExpressions.length === 1) return searchableExpressions[0] ?? "()";
2217
2197
  return `(${searchableExpressions.join(", ")})`;
2218
2198
  }
2219
- function buildTokenizedSearchPredicate(params) {
2220
- const { searchableNodesExpression, termsExpression, isCaseSensitive } = params;
2221
- return `(every $term in ${termsExpression}
2222
- satisfies some $searchNode in (${searchableNodesExpression})
2223
- satisfies cts:contains(
2224
- $searchNode,
2225
- ${buildCtsWordQueryExpression({
2226
- termExpression: "$term",
2227
- isCaseSensitive
2228
- })}
2229
- ))`;
2199
+ function buildPropertyValueNodesExpression(params) {
2200
+ const predicates = [];
2201
+ if (params?.excludeInherited) predicates.push(`not(@inherited="true")`);
2202
+ for (const excludedDataType of params?.excludedDataTypes ?? []) predicates.push(`not(@dataType=${stringLiteral(excludedDataType)})`);
2203
+ if (predicates.length === 0) return "value";
2204
+ return `value[${predicates.join(" and ")}]`;
2205
+ }
2206
+ function buildPropertyValueContentNodesExpression(params) {
2207
+ const { language, excludeInherited, excludedDataTypes } = params;
2208
+ return `${buildPropertyValueNodesExpression({
2209
+ excludeInherited,
2210
+ excludedDataTypes
2211
+ })}/content[@xml:lang="${language}"]`;
2212
+ }
2213
+ function buildPropertyValueContentValuesExpression(params) {
2214
+ return buildFlattenedContentValuesExpression(buildPropertyValueContentNodesExpression(params));
2215
+ }
2216
+ function buildPropertyValueRawValuesExpression(params) {
2217
+ return `for $value in ${buildPropertyValueNodesExpression(params)}[@rawValue]
2218
+ return string($value/@rawValue)`;
2219
+ }
2220
+ function buildPropertyValueDirectTextValuesExpression(params) {
2221
+ return `for $value in ${buildPropertyValueNodesExpression(params)}
2222
+ let $candidate := string-join($value/text(), "")
2223
+ where normalize-space($candidate) != ""
2224
+ return $candidate`;
2225
+ }
2226
+ function buildPropertyScalarValueExpressions() {
2227
+ return [buildPropertyValueRawValuesExpression(), buildPropertyValueDirectTextValuesExpression()];
2228
+ }
2229
+ function buildAllPropertyValueExpressions(language) {
2230
+ const params = { excludedDataTypes: ["IDREF"] };
2231
+ return [
2232
+ buildPropertyValueRawValuesExpression(params),
2233
+ buildPropertyValueContentValuesExpression({
2234
+ language,
2235
+ ...params
2236
+ }),
2237
+ buildPropertyValueDirectTextValuesExpression(params)
2238
+ ];
2230
2239
  }
2231
2240
  /**
2232
2241
  * Build a string match predicate for an XQuery string.
@@ -2236,10 +2245,16 @@ function buildRawStringMatchPredicate(params) {
2236
2245
  const comparedValueLiteral = stringLiteral(isCaseSensitive ? value : value.toLowerCase());
2237
2246
  const candidateVar = "$candidate";
2238
2247
  const comparedCandidate = isCaseSensitive ? candidateVar : `lower-case(${candidateVar})`;
2239
- if (matchMode === "includes") return `some ${candidateVar} in (${valueExpression})
2240
- satisfies contains(${comparedCandidate}, ${comparedValueLiteral})`;
2241
- return `some ${candidateVar} in (${valueExpression})
2242
- satisfies ${comparedCandidate} = ${comparedValueLiteral}`;
2248
+ if (matchMode === "includes") return `exists(
2249
+ for ${candidateVar} in (${valueExpression})
2250
+ where contains(${comparedCandidate}, ${comparedValueLiteral})
2251
+ return ${candidateVar}
2252
+ )`;
2253
+ return `exists(
2254
+ for ${candidateVar} in (${valueExpression})
2255
+ where ${comparedCandidate} = ${comparedValueLiteral}
2256
+ return ${candidateVar}
2257
+ )`;
2243
2258
  }
2244
2259
  /**
2245
2260
  * Build a combined raw string match predicate for multiple paths.
@@ -2247,30 +2262,53 @@ function buildRawStringMatchPredicate(params) {
2247
2262
  function buildCombinedRawStringMatchPredicate(params) {
2248
2263
  const { valueExpressions, value, matchMode, isCaseSensitive } = params;
2249
2264
  const predicates = [];
2250
- for (const valueExpression of valueExpressions) predicates.push(buildRawStringMatchPredicate({
2265
+ for (const valueExpression of valueExpressions) predicates.push(`(${buildRawStringMatchPredicate({
2251
2266
  valueExpression,
2252
2267
  value,
2253
2268
  matchMode,
2254
2269
  isCaseSensitive
2255
- }));
2270
+ })})`);
2256
2271
  return buildOrPredicate(predicates);
2257
2272
  }
2258
2273
  /**
2259
2274
  * Build CTS word-query options for API v2 includes search.
2260
2275
  */
2261
- function buildCtsQueryOptionsExpression(isCaseSensitive) {
2262
- return `(${[
2276
+ function buildCtsQueryOptionsExpression(params) {
2277
+ const { isCaseSensitive, isWildcarded } = params;
2278
+ const options = [
2263
2279
  isCaseSensitive ? "case-sensitive" : "case-insensitive",
2264
2280
  "diacritic-insensitive",
2265
2281
  "punctuation-insensitive"
2266
- ].map((option) => stringLiteral(option)).join(", ")})`;
2282
+ ];
2283
+ if (isWildcarded) options.push("wildcarded");
2284
+ return `(${options.map((option) => stringLiteral(option)).join(", ")})`;
2285
+ }
2286
+ function buildWildcardedTermExpression(termExpression) {
2287
+ return `fn:concat("*", ${termExpression}, "*")`;
2267
2288
  }
2268
2289
  /**
2269
2290
  * Build a CTS word-query expression for an XQuery term.
2270
2291
  */
2271
2292
  function buildCtsWordQueryExpression(params) {
2272
- const { termExpression, isCaseSensitive } = params;
2273
- return `cts:word-query(${termExpression}, ${buildCtsQueryOptionsExpression(isCaseSensitive)})`;
2293
+ const { termExpression, isCaseSensitive, isWildcarded } = params;
2294
+ return `cts:word-query(${isWildcarded ? buildWildcardedTermExpression(termExpression) : termExpression}, ${buildCtsQueryOptionsExpression({
2295
+ isCaseSensitive,
2296
+ isWildcarded
2297
+ })})`;
2298
+ }
2299
+ function buildCtsElementWordQueryExpression(params) {
2300
+ const { elementName, termExpression, isCaseSensitive, isWildcarded } = params;
2301
+ return `cts:element-word-query(xs:QName("${elementName}"), ${isWildcarded ? buildWildcardedTermExpression(termExpression) : termExpression}, ${buildCtsQueryOptionsExpression({
2302
+ isCaseSensitive,
2303
+ isWildcarded
2304
+ })})`;
2305
+ }
2306
+ function buildCtsElementAttributeWordQueryExpression(params) {
2307
+ const { elementName, attributeName, termExpression, isCaseSensitive, isWildcarded } = params;
2308
+ return `cts:element-attribute-word-query(xs:QName("${elementName}"), xs:QName("${attributeName}"), ${isWildcarded ? buildWildcardedTermExpression(termExpression) : termExpression}, ${buildCtsQueryOptionsExpression({
2309
+ isCaseSensitive,
2310
+ isWildcarded
2311
+ })})`;
2274
2312
  }
2275
2313
  /**
2276
2314
  * Build tokenized search declarations for CTS-backed queries.
@@ -2294,8 +2332,8 @@ function buildTokenizedSearchDeclarations(params) {
2294
2332
  termsVar
2295
2333
  };
2296
2334
  }
2297
- function buildTokenizedCtsClause(params) {
2298
- const { value, isCaseSensitive, queryKey, fallbackPredicate, buildTermQueryExpression, buildTokenPredicate } = params;
2335
+ function buildCtsCandidateQuery(params) {
2336
+ const { value, isCaseSensitive, queryKey, buildCandidateTermQuery } = params;
2299
2337
  const tokenizedSearchDeclarations = buildTokenizedSearchDeclarations({
2300
2338
  value,
2301
2339
  isCaseSensitive,
@@ -2308,7 +2346,7 @@ function buildTokenizedCtsClause(params) {
2308
2346
  isCaseSensitive
2309
2347
  }).length === 0) return {
2310
2348
  declarations: [],
2311
- predicate: fallbackPredicate,
2349
+ termsVar: tokenizedSearchDeclarations.termsVar,
2312
2350
  candidateQueryVar: null
2313
2351
  };
2314
2352
  return {
@@ -2316,7 +2354,7 @@ function buildTokenizedCtsClause(params) {
2316
2354
  ...tokenizedSearchDeclarations.declarations,
2317
2355
  `let ${termQueriesVar} :=
2318
2356
  for $term in ${tokenizedSearchDeclarations.termsVar}
2319
- return ${buildTermQueryExpression("$term")}`,
2357
+ return ${buildCandidateTermQuery("$term")}`,
2320
2358
  `let ${candidateQueryVar} :=
2321
2359
  if (count(${tokenizedSearchDeclarations.termsVar}) = 1)
2322
2360
  then ${termQueriesVar}[1]
@@ -2324,29 +2362,42 @@ function buildTokenizedCtsClause(params) {
2324
2362
  then cts:and-query(${termQueriesVar})
2325
2363
  else ()`
2326
2364
  ],
2327
- predicate: buildTokenPredicate(tokenizedSearchDeclarations.termsVar),
2365
+ termsVar: tokenizedSearchDeclarations.termsVar,
2328
2366
  candidateQueryVar
2329
2367
  };
2330
2368
  }
2331
2369
  function buildCtsIncludesPredicate(params) {
2332
2370
  const { searchableNodesExpression, fallbackValueExpression, value, isCaseSensitive, queryKey, buildCandidateTermQuery } = params;
2333
- return buildTokenizedCtsClause({
2371
+ const candidateQuery = buildCtsCandidateQuery({
2334
2372
  value,
2335
2373
  isCaseSensitive,
2336
2374
  queryKey,
2337
- fallbackPredicate: buildRawStringMatchPredicate({
2338
- valueExpression: fallbackValueExpression,
2339
- value,
2340
- matchMode: "includes",
2341
- isCaseSensitive
2342
- }),
2343
- buildTermQueryExpression: buildCandidateTermQuery,
2344
- buildTokenPredicate: (termsExpression) => buildTokenizedSearchPredicate({
2345
- searchableNodesExpression,
2346
- termsExpression,
2347
- isCaseSensitive
2348
- })
2375
+ buildCandidateTermQuery
2376
+ });
2377
+ const fallbackPredicate = buildRawStringMatchPredicate({
2378
+ valueExpression: fallbackValueExpression,
2379
+ value,
2380
+ matchMode: "includes",
2381
+ isCaseSensitive
2349
2382
  });
2383
+ if (candidateQuery.candidateQueryVar == null) return {
2384
+ declarations: [],
2385
+ predicate: fallbackPredicate,
2386
+ candidateQueryVar: null
2387
+ };
2388
+ return {
2389
+ declarations: candidateQuery.declarations,
2390
+ predicate: `(every $term in ${candidateQuery.termsVar}
2391
+ satisfies some $searchNode in (${searchableNodesExpression})
2392
+ satisfies cts:contains(
2393
+ $searchNode,
2394
+ ${buildCtsWordQueryExpression({
2395
+ termExpression: "$term",
2396
+ isCaseSensitive
2397
+ })}
2398
+ ))`,
2399
+ candidateQueryVar: candidateQuery.candidateQueryVar
2400
+ };
2350
2401
  }
2351
2402
  /**
2352
2403
  * Build the raw search paths for item-level string search.
@@ -2400,14 +2451,14 @@ function buildItemStringPropertyValueBranch(params) {
2400
2451
  * Build an item-level CTS string search predicate.
2401
2452
  */
2402
2453
  function buildItemStringSearchClause(params) {
2403
- const { query, version, queryKey } = params;
2454
+ const { query, version, queryKey, allowCtsPredicates } = params;
2404
2455
  const fallbackPredicate = buildCombinedRawStringMatchPredicate({
2405
2456
  valueExpressions: buildItemStringSearchPaths(query.language),
2406
2457
  value: query.value,
2407
2458
  matchMode: query.matchMode,
2408
2459
  isCaseSensitive: query.isCaseSensitive
2409
2460
  });
2410
- if (query.matchMode !== "includes" || version !== 2) return {
2461
+ if (!allowCtsPredicates || query.matchMode !== "includes" || version !== 2) return {
2411
2462
  declarations: [],
2412
2463
  predicate: fallbackPredicate,
2413
2464
  candidateQueryVar: null
@@ -2471,9 +2522,6 @@ function getQueryGroupChildren(query) {
2471
2522
  function getQueryGroupOperator(query) {
2472
2523
  return "and" in query ? "and" : "or";
2473
2524
  }
2474
- function getContentTargetConfig(target) {
2475
- return CONTENT_TARGET_CONFIGS[target];
2476
- }
2477
2525
  function buildContentTargetCandidateBranch(params) {
2478
2526
  const { containerElementName, termExpression, isCaseSensitive, language } = params;
2479
2527
  return `cts:element-query(xs:QName("${containerElementName}"),
@@ -2507,114 +2555,28 @@ function buildPropertyStringCandidateBranch(params) {
2507
2555
  )
2508
2556
  )`;
2509
2557
  }
2510
- function buildPropertyContentNodesExpression(params) {
2511
- const { language, excludeInherited } = params;
2512
- return `${excludeInherited ? `value[not(@inherited="true")]` : "value"}/content[@xml:lang="${language}"]`;
2513
- }
2514
- function buildPropertyRawValueCandidateBranch(params) {
2558
+ function buildPropertyScalarCandidateBranch(params) {
2515
2559
  const { termExpression, isCaseSensitive } = params;
2516
2560
  return `cts:element-query(xs:QName("properties"),
2517
2561
  cts:element-query(xs:QName("property"),
2518
- cts:element-attribute-word-query(
2519
- xs:QName("value"),
2520
- xs:QName("rawValue"),
2521
- ${termExpression},
2522
- ${buildCtsQueryOptionsExpression(isCaseSensitive)}
2523
- )
2524
- )
2525
- )`;
2526
- }
2527
- function buildPropertySimpleValueTextCandidateBranch(params) {
2528
- const { termExpression, isCaseSensitive } = params;
2529
- return `cts:element-query(xs:QName("properties"),
2530
- cts:element-query(xs:QName("property"),
2531
- cts:element-word-query(
2532
- xs:QName("value"),
2533
- ${termExpression},
2534
- ${buildCtsQueryOptionsExpression(isCaseSensitive)}
2535
- )
2562
+ cts:or-query((
2563
+ ${buildCtsElementAttributeWordQueryExpression({
2564
+ elementName: "value",
2565
+ attributeName: "rawValue",
2566
+ termExpression,
2567
+ isCaseSensitive,
2568
+ isWildcarded: true
2569
+ })},
2570
+ ${buildCtsElementWordQueryExpression({
2571
+ elementName: "value",
2572
+ termExpression,
2573
+ isCaseSensitive,
2574
+ isWildcarded: true
2575
+ })}
2576
+ ))
2536
2577
  )
2537
2578
  )`;
2538
2579
  }
2539
- function buildPropertySimpleValueRawValueExpression() {
2540
- return buildNodeStringValuesExpression("value/@rawValue");
2541
- }
2542
- function buildPropertySimpleValueTextExpression() {
2543
- return buildNodeStringValuesExpression("value[not(*)]/text()");
2544
- }
2545
- function buildPropertySimpleValueSearchableNodesExpression() {
2546
- return "value[not(*)]";
2547
- }
2548
- function buildPropertyContentRawPredicate(params) {
2549
- const { value, matchMode, isCaseSensitive, language, excludeInherited } = params;
2550
- return buildRawStringMatchPredicate({
2551
- valueExpression: buildFlattenedContentValuesExpression(buildPropertyContentNodesExpression({
2552
- language,
2553
- excludeInherited
2554
- })),
2555
- value,
2556
- matchMode,
2557
- isCaseSensitive
2558
- });
2559
- }
2560
- function buildPropertyRawValueOrTextRawPredicate(params) {
2561
- const { value, matchMode, isCaseSensitive } = params;
2562
- return buildOrPredicate([buildRawStringMatchPredicate({
2563
- valueExpression: buildPropertySimpleValueRawValueExpression(),
2564
- value,
2565
- matchMode,
2566
- isCaseSensitive
2567
- }), buildRawStringMatchPredicate({
2568
- valueExpression: buildPropertySimpleValueTextExpression(),
2569
- value,
2570
- matchMode,
2571
- isCaseSensitive
2572
- })]);
2573
- }
2574
- function buildPropertyRawValueOrTextTokenPredicate(params) {
2575
- const { termsExpression, isCaseSensitive } = params;
2576
- const rawValueQuery = `cts:element-attribute-word-query(
2577
- xs:QName("value"),
2578
- xs:QName("rawValue"),
2579
- $term,
2580
- ${buildCtsQueryOptionsExpression(isCaseSensitive)}
2581
- )`;
2582
- const textQuery = `cts:element-word-query(
2583
- xs:QName("value"),
2584
- $term,
2585
- ${buildCtsQueryOptionsExpression(isCaseSensitive)}
2586
- )`;
2587
- return `(every $term in ${termsExpression}
2588
- satisfies some $searchNode in (${buildPropertySimpleValueSearchableNodesExpression()})
2589
- satisfies (
2590
- cts:contains($searchNode, ${rawValueQuery})
2591
- or
2592
- cts:contains($searchNode, ${textQuery})
2593
- ))`;
2594
- }
2595
- function buildPropertyContentTokenPredicate(params) {
2596
- const { termsExpression, isCaseSensitive, language, excludeInherited } = params;
2597
- return buildTokenizedSearchPredicate({
2598
- searchableNodesExpression: buildSearchableContentNodesExpression(buildPropertyContentNodesExpression({
2599
- language,
2600
- excludeInherited
2601
- })),
2602
- termsExpression,
2603
- isCaseSensitive
2604
- });
2605
- }
2606
- function buildIncludesGroupMemberFromTokenSource(params) {
2607
- const { rawPredicate, searchableNodesExpression, isCaseSensitive, buildCandidateTermQuery } = params;
2608
- return {
2609
- rawPredicate,
2610
- buildTokenPredicate: (termsExpression) => buildTokenizedSearchPredicate({
2611
- searchableNodesExpression,
2612
- termsExpression,
2613
- isCaseSensitive
2614
- }),
2615
- buildCandidateTermQuery
2616
- };
2617
- }
2618
2580
  function getGroupableIncludesValue(query) {
2619
2581
  switch (query.target) {
2620
2582
  case "string":
@@ -2624,9 +2586,8 @@ function getGroupableIncludesValue(query) {
2624
2586
  case "periods":
2625
2587
  case "bibliography": return query.value;
2626
2588
  case "property":
2627
- if (query.dataType === "IDREF") return null;
2628
- if (query.dataType === "date" || query.dataType === "dateTime") return "value" in query && query.value != null ? query.value : null;
2629
- return query.value ?? null;
2589
+ if (query.dataType !== "string" || query.value == null) return null;
2590
+ return query.value;
2630
2591
  }
2631
2592
  }
2632
2593
  function isCompatibleIncludesGroupQuery(params) {
@@ -2636,36 +2597,30 @@ function isCompatibleIncludesGroupQuery(params) {
2636
2597
  return queryValue != null && queryValue === value && query.isCaseSensitive === isCaseSensitive && query.language === language;
2637
2598
  }
2638
2599
  function buildContentTargetIncludesGroupMember(params) {
2639
- const { query } = params;
2640
- const config = getContentTargetConfig(query.target);
2641
- const contentNodesExpression = config.getContentNodesExpression(query.language);
2642
- return buildIncludesGroupMemberFromTokenSource({
2600
+ const { contentNodesExpression, value, isCaseSensitive, language, containerElementName } = params;
2601
+ return {
2643
2602
  rawPredicate: buildRawStringMatchPredicate({
2644
2603
  valueExpression: buildFlattenedContentValuesExpression(contentNodesExpression),
2645
- value: query.value,
2604
+ value,
2646
2605
  matchMode: "includes",
2647
- isCaseSensitive: query.isCaseSensitive
2606
+ isCaseSensitive
2648
2607
  }),
2649
- searchableNodesExpression: buildSearchableContentNodesExpression(contentNodesExpression),
2650
- isCaseSensitive: query.isCaseSensitive,
2651
2608
  buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
2652
- containerElementName: config.containerElementName,
2609
+ containerElementName,
2653
2610
  termExpression,
2654
- isCaseSensitive: query.isCaseSensitive,
2655
- language: query.language
2611
+ isCaseSensitive,
2612
+ language
2656
2613
  })
2657
- });
2614
+ };
2658
2615
  }
2659
2616
  function buildItemStringIncludesGroupMember(query) {
2660
- return buildIncludesGroupMemberFromTokenSource({
2617
+ return {
2661
2618
  rawPredicate: buildCombinedRawStringMatchPredicate({
2662
2619
  valueExpressions: buildItemStringSearchPaths(query.language),
2663
2620
  value: query.value,
2664
2621
  matchMode: "includes",
2665
2622
  isCaseSensitive: query.isCaseSensitive
2666
2623
  }),
2667
- searchableNodesExpression: buildItemStringSearchableNodesExpression(query.language),
2668
- isCaseSensitive: query.isCaseSensitive,
2669
2624
  buildCandidateTermQuery: (termExpression) => `cts:or-query((
2670
2625
  ${buildItemStringIdentificationBranch({
2671
2626
  termExpression,
@@ -2678,112 +2633,71 @@ function buildItemStringIncludesGroupMember(query) {
2678
2633
  language: query.language
2679
2634
  })}
2680
2635
  ))`
2681
- });
2636
+ };
2682
2637
  }
2683
2638
  function buildPropertyStringIncludesGroupMember(query) {
2684
2639
  const propertyVariable = query.propertyVariable;
2685
2640
  const predicateParts = [];
2686
- const propertyValue = query.value;
2641
+ const valueExpression = buildPropertyValueContentValuesExpression({
2642
+ language: query.language,
2643
+ excludeInherited: true
2644
+ });
2687
2645
  if (propertyVariable != null) predicateParts.push(buildPropertyLabelPredicate(propertyVariable));
2688
- return buildIncludesGroupMemberFromTokenSource({
2689
- rawPredicate: buildPropertyPredicateExpression([...predicateParts, buildPropertyContentRawPredicate({
2690
- value: propertyValue,
2646
+ return {
2647
+ rawPredicate: buildPropertyPredicateExpression([...predicateParts, buildRawStringMatchPredicate({
2648
+ valueExpression,
2649
+ value: query.value,
2691
2650
  matchMode: "includes",
2692
- isCaseSensitive: query.isCaseSensitive,
2693
- language: query.language,
2694
- excludeInherited: true
2651
+ isCaseSensitive: query.isCaseSensitive
2695
2652
  })]),
2696
- searchableNodesExpression: buildPropertyPredicateExpression([...predicateParts, buildSearchableContentNodesExpression(buildPropertyContentNodesExpression({
2697
- language: query.language,
2698
- excludeInherited: true
2699
- }))]),
2700
- isCaseSensitive: query.isCaseSensitive,
2701
2653
  buildCandidateTermQuery: (termExpression) => buildPropertyStringCandidateBranch({
2702
2654
  termExpression,
2703
2655
  isCaseSensitive: query.isCaseSensitive,
2704
2656
  language: query.language
2705
2657
  })
2706
- });
2707
- }
2708
- function buildPropertyRawValueOrTextIncludesGroupMember(query) {
2709
- const propertyVariable = query.propertyVariable;
2710
- const predicateParts = [];
2711
- const propertyValue = getGroupableIncludesValue(query);
2712
- if (propertyValue == null) throw new Error("Cannot build a rawValue/text includes group without a search value");
2713
- if (propertyVariable != null) predicateParts.push(buildPropertyLabelPredicate(propertyVariable));
2714
- return {
2715
- rawPredicate: buildPropertyPredicateExpression([...predicateParts, buildPropertyRawValueOrTextRawPredicate({
2716
- value: propertyValue,
2717
- matchMode: "includes",
2718
- isCaseSensitive: query.isCaseSensitive
2719
- })]),
2720
- buildTokenPredicate: (termsExpression) => buildPropertyPredicateExpression([...predicateParts, buildPropertyRawValueOrTextTokenPredicate({
2721
- termsExpression,
2722
- isCaseSensitive: query.isCaseSensitive
2723
- })]),
2724
- buildCandidateTermQuery: (termExpression) => buildOrCtsQueryExpression([buildPropertyRawValueCandidateBranch({
2725
- termExpression,
2726
- isCaseSensitive: query.isCaseSensitive
2727
- }), buildPropertySimpleValueTextCandidateBranch({
2728
- termExpression,
2729
- isCaseSensitive: query.isCaseSensitive
2730
- })])
2731
2658
  };
2732
2659
  }
2733
- function buildPropertyAllIncludesGroupMember(query) {
2734
- const propertyVariable = query.propertyVariable;
2735
- const predicateParts = [];
2736
- if (propertyVariable != null) predicateParts.push(buildPropertyLabelPredicate(propertyVariable));
2737
- return {
2738
- rawPredicate: buildPropertyPredicateExpression([...predicateParts, buildOrPredicate([buildPropertyRawValueOrTextRawPredicate({
2660
+ function buildIncludesGroupMember(query) {
2661
+ switch (query.target) {
2662
+ case "string": return buildItemStringIncludesGroupMember(query);
2663
+ case "title": return buildContentTargetIncludesGroupMember({
2664
+ contentNodesExpression: `identification/label/content[@xml:lang="${query.language}"]`,
2739
2665
  value: query.value,
2740
- matchMode: "includes",
2741
- isCaseSensitive: query.isCaseSensitive
2742
- }), buildPropertyContentRawPredicate({
2666
+ isCaseSensitive: query.isCaseSensitive,
2667
+ language: query.language,
2668
+ containerElementName: "identification"
2669
+ });
2670
+ case "description": return buildContentTargetIncludesGroupMember({
2671
+ contentNodesExpression: `description/content[@xml:lang="${query.language}"]`,
2743
2672
  value: query.value,
2744
- matchMode: "includes",
2745
2673
  isCaseSensitive: query.isCaseSensitive,
2746
2674
  language: query.language,
2747
- excludeInherited: true
2748
- })])]),
2749
- buildTokenPredicate: (termsExpression) => buildPropertyPredicateExpression([...predicateParts, buildOrPredicate([buildPropertyRawValueOrTextTokenPredicate({
2750
- termsExpression,
2751
- isCaseSensitive: query.isCaseSensitive
2752
- }), buildPropertyContentTokenPredicate({
2753
- termsExpression,
2675
+ containerElementName: "description"
2676
+ });
2677
+ case "periods": return buildContentTargetIncludesGroupMember({
2678
+ contentNodesExpression: `periods/period/identification/label/content[@xml:lang="${query.language}"]`,
2679
+ value: query.value,
2754
2680
  isCaseSensitive: query.isCaseSensitive,
2755
2681
  language: query.language,
2756
- excludeInherited: true
2757
- })])]),
2758
- buildCandidateTermQuery: (termExpression) => buildOrCtsQueryExpression([
2759
- buildPropertyRawValueCandidateBranch({
2760
- termExpression,
2761
- isCaseSensitive: query.isCaseSensitive
2762
- }),
2763
- buildPropertySimpleValueTextCandidateBranch({
2764
- termExpression,
2765
- isCaseSensitive: query.isCaseSensitive
2766
- }),
2767
- buildPropertyStringCandidateBranch({
2768
- termExpression,
2769
- isCaseSensitive: query.isCaseSensitive,
2770
- language: query.language
2771
- })
2772
- ])
2773
- };
2774
- }
2775
- function buildIncludesGroupMember(query) {
2776
- switch (query.target) {
2777
- case "string": return buildItemStringIncludesGroupMember(query);
2778
- case "title":
2779
- case "description":
2780
- case "periods":
2781
- case "bibliography":
2782
- case "image": return buildContentTargetIncludesGroupMember({ query });
2682
+ containerElementName: "period"
2683
+ });
2684
+ case "bibliography": return buildContentTargetIncludesGroupMember({
2685
+ contentNodesExpression: `bibliographies/bibliography/identification/label/content[@xml:lang="${query.language}"]`,
2686
+ value: query.value,
2687
+ isCaseSensitive: query.isCaseSensitive,
2688
+ language: query.language,
2689
+ containerElementName: "bibliography"
2690
+ });
2691
+ case "image": return buildContentTargetIncludesGroupMember({
2692
+ contentNodesExpression: `image/identification/label/content[@xml:lang="${query.language}"]`,
2693
+ value: query.value,
2694
+ isCaseSensitive: query.isCaseSensitive,
2695
+ language: query.language,
2696
+ containerElementName: "image"
2697
+ });
2783
2698
  case "property":
2784
- if (query.dataType === "string") return buildPropertyStringIncludesGroupMember(query);
2785
- if (query.dataType === "all") return buildPropertyAllIncludesGroupMember(query);
2786
- return buildPropertyRawValueOrTextIncludesGroupMember(query);
2699
+ if (query.dataType === "all") throw new Error(`Property queries with dataType "all" are not compatible with includes-group optimization`);
2700
+ return buildPropertyStringIncludesGroupMember(query);
2787
2701
  }
2788
2702
  }
2789
2703
  function getCompatibleIncludesGroupLeaves(params) {
@@ -2820,22 +2734,46 @@ function buildIncludesGroupClause(params) {
2820
2734
  const groupValue = getGroupableIncludesValue(firstQuery);
2821
2735
  if (groupValue == null) throw new Error("Cannot build an includes group without a search value");
2822
2736
  const members = queries.map((query) => buildIncludesGroupMember(query));
2823
- return buildTokenizedCtsClause({
2737
+ const tokenizedSearchDeclarations = buildTokenizedSearchDeclarations({
2824
2738
  value: groupValue,
2825
2739
  isCaseSensitive: firstQuery.isCaseSensitive,
2826
- queryKey,
2827
- fallbackPredicate: buildOrPredicate(members.map((member) => member.rawPredicate)),
2828
- buildTermQueryExpression: (termExpression) => buildOrCtsQueryExpression(members.map((member) => member.buildCandidateTermQuery(termExpression))),
2829
- buildTokenPredicate: (termsExpression) => buildOrPredicate(members.map((member) => member.buildTokenPredicate(termsExpression)))
2740
+ queryKey
2830
2741
  });
2742
+ const tokenizedTerms = tokenizeIncludesSearchValue({
2743
+ value: groupValue,
2744
+ isCaseSensitive: firstQuery.isCaseSensitive
2745
+ });
2746
+ const termQueriesVar = `$query${queryKey}TermQueries`;
2747
+ const candidateQueryVar = `$query${queryKey}CandidateQuery`;
2748
+ if (tokenizedTerms.length === 0) return {
2749
+ declarations: [],
2750
+ predicate: buildOrPredicate(members.map((member) => member.rawPredicate)),
2751
+ candidateQueryVar: null
2752
+ };
2753
+ return {
2754
+ declarations: [
2755
+ ...tokenizedSearchDeclarations.declarations,
2756
+ `let ${termQueriesVar} :=
2757
+ for $term in ${tokenizedSearchDeclarations.termsVar}
2758
+ return ${buildOrCtsQueryExpression(members.map((member) => member.buildCandidateTermQuery("$term")))}`,
2759
+ `let ${candidateQueryVar} :=
2760
+ if (count(${tokenizedSearchDeclarations.termsVar}) = 1)
2761
+ then ${termQueriesVar}[1]
2762
+ else if (count(${tokenizedSearchDeclarations.termsVar}) gt 1)
2763
+ then cts:and-query(${termQueriesVar})
2764
+ else ()`
2765
+ ],
2766
+ predicate: `cts:contains(., ${candidateQueryVar})`,
2767
+ candidateQueryVar
2768
+ };
2831
2769
  }
2832
2770
  /**
2833
2771
  * Build a string match predicate for an XQuery string.
2834
2772
  */
2835
2773
  function buildStringMatchClause(params) {
2836
- const { contentNodesExpression, value, matchMode, isCaseSensitive, version, queryKey, buildCandidateTermQuery } = params;
2774
+ const { contentNodesExpression, value, matchMode, isCaseSensitive, version, queryKey, allowCtsPredicates, buildCandidateTermQuery } = params;
2837
2775
  const valueExpression = buildFlattenedContentValuesExpression(contentNodesExpression);
2838
- if (matchMode === "includes" && version === 2 && buildCandidateTermQuery != null) return buildCtsIncludesPredicate({
2776
+ if (allowCtsPredicates && matchMode === "includes" && version === 2 && buildCandidateTermQuery != null) return buildCtsIncludesPredicate({
2839
2777
  searchableNodesExpression: buildSearchableContentNodesExpression(contentNodesExpression),
2840
2778
  fallbackValueExpression: valueExpression,
2841
2779
  value,
@@ -2854,35 +2792,36 @@ function buildStringMatchClause(params) {
2854
2792
  candidateQueryVar: null
2855
2793
  };
2856
2794
  }
2857
- function buildPropertyValueAttributePredicate(params) {
2858
- const { value, attributeName } = params;
2859
- return `value[@${attributeName}=${stringLiteral(value)}]`;
2860
- }
2861
- function buildPropertyRawValueOrTextMatchClause(params) {
2862
- const { value, matchMode, isCaseSensitive, version, queryKey } = params;
2863
- const fallbackPredicate = buildPropertyRawValueOrTextRawPredicate(params);
2864
- if (matchMode !== "includes" || version !== 2) return {
2795
+ function buildCombinedRawStringMatchClause(params) {
2796
+ const { valueExpressions, value, matchMode, isCaseSensitive, version, queryKey, allowCtsPredicates, buildCandidateTermQuery } = params;
2797
+ const predicate = buildCombinedRawStringMatchPredicate({
2798
+ valueExpressions,
2799
+ value,
2800
+ matchMode,
2801
+ isCaseSensitive
2802
+ });
2803
+ if (allowCtsPredicates && matchMode === "includes" && version === 2 && buildCandidateTermQuery != null) {
2804
+ const candidateQuery = buildCtsCandidateQuery({
2805
+ value,
2806
+ isCaseSensitive,
2807
+ queryKey,
2808
+ buildCandidateTermQuery
2809
+ });
2810
+ return {
2811
+ declarations: candidateQuery.declarations,
2812
+ predicate,
2813
+ candidateQueryVar: candidateQuery.candidateQueryVar
2814
+ };
2815
+ }
2816
+ return {
2865
2817
  declarations: [],
2866
- predicate: fallbackPredicate,
2818
+ predicate,
2867
2819
  candidateQueryVar: null
2868
2820
  };
2869
- return buildTokenizedCtsClause({
2870
- value,
2871
- isCaseSensitive,
2872
- queryKey,
2873
- fallbackPredicate,
2874
- buildTermQueryExpression: (termExpression) => buildOrCtsQueryExpression([buildPropertyRawValueCandidateBranch({
2875
- termExpression,
2876
- isCaseSensitive
2877
- }), buildPropertySimpleValueTextCandidateBranch({
2878
- termExpression,
2879
- isCaseSensitive
2880
- })]),
2881
- buildTokenPredicate: (termsExpression) => buildPropertyRawValueOrTextTokenPredicate({
2882
- termsExpression,
2883
- isCaseSensitive
2884
- })
2885
- });
2821
+ }
2822
+ function buildPropertyValueAttributePredicate(params) {
2823
+ const { propertyValue, attributeName } = params;
2824
+ return `value[@${attributeName}=${stringLiteral(propertyValue)}]`;
2886
2825
  }
2887
2826
  function buildPropertyPredicateExpression(propertyPredicates) {
2888
2827
  let propertyExpression = ".//properties//property";
@@ -2890,17 +2829,18 @@ function buildPropertyPredicateExpression(propertyPredicates) {
2890
2829
  return propertyExpression;
2891
2830
  }
2892
2831
  function buildPropertyStringValueClause(params) {
2893
- const { query, version, queryKey } = params;
2832
+ const { query, version, queryKey, allowCtsPredicates } = params;
2894
2833
  return buildStringMatchClause({
2895
- contentNodesExpression: buildPropertyContentNodesExpression({
2834
+ contentNodesExpression: buildPropertyValueContentNodesExpression({
2896
2835
  language: query.language,
2897
- excludeInherited: query.matchMode === "includes" && version === 2
2836
+ excludeInherited: allowCtsPredicates && query.matchMode === "includes" && version === 2
2898
2837
  }),
2899
2838
  value: query.value,
2900
2839
  matchMode: query.matchMode,
2901
2840
  isCaseSensitive: query.isCaseSensitive,
2902
2841
  version,
2903
2842
  queryKey,
2843
+ allowCtsPredicates,
2904
2844
  buildCandidateTermQuery: (termExpression) => buildPropertyStringCandidateBranch({
2905
2845
  termExpression,
2906
2846
  isCaseSensitive: query.isCaseSensitive,
@@ -2908,73 +2848,57 @@ function buildPropertyStringValueClause(params) {
2908
2848
  })
2909
2849
  });
2910
2850
  }
2911
- function buildPropertyAllValueClause(params) {
2912
- const { query, version, queryKey } = params;
2913
- const excludeInheritedContent = query.matchMode === "includes" && version === 2;
2914
- const fallbackPredicate = buildOrPredicate([buildPropertyRawValueOrTextRawPredicate({
2915
- value: query.value,
2916
- matchMode: query.matchMode,
2917
- isCaseSensitive: query.isCaseSensitive
2918
- }), buildPropertyContentRawPredicate({
2919
- value: query.value,
2920
- matchMode: query.matchMode,
2921
- isCaseSensitive: query.isCaseSensitive,
2922
- language: query.language,
2923
- excludeInherited: excludeInheritedContent
2924
- })]);
2925
- if (query.matchMode !== "includes" || version !== 2) return {
2851
+ function buildPropertyScalarValueClause(params) {
2852
+ const { value, matchMode, isCaseSensitive, version, queryKey, allowCtsPredicates } = params;
2853
+ return buildCombinedRawStringMatchClause({
2854
+ valueExpressions: buildPropertyScalarValueExpressions(),
2855
+ value,
2856
+ matchMode,
2857
+ isCaseSensitive,
2858
+ version,
2859
+ queryKey,
2860
+ allowCtsPredicates,
2861
+ buildCandidateTermQuery: (termExpression) => buildPropertyScalarCandidateBranch({
2862
+ termExpression,
2863
+ isCaseSensitive
2864
+ })
2865
+ });
2866
+ }
2867
+ function buildPropertyAllValueClause(query) {
2868
+ return {
2926
2869
  declarations: [],
2927
- predicate: fallbackPredicate,
2870
+ predicate: buildCombinedRawStringMatchPredicate({
2871
+ valueExpressions: buildAllPropertyValueExpressions(query.language),
2872
+ value: query.value,
2873
+ matchMode: query.matchMode,
2874
+ isCaseSensitive: query.isCaseSensitive
2875
+ }),
2928
2876
  candidateQueryVar: null
2929
2877
  };
2930
- return buildTokenizedCtsClause({
2931
- value: query.value,
2932
- isCaseSensitive: query.isCaseSensitive,
2933
- queryKey,
2934
- fallbackPredicate,
2935
- buildTermQueryExpression: (termExpression) => buildOrCtsQueryExpression([
2936
- buildPropertyRawValueCandidateBranch({
2937
- termExpression,
2938
- isCaseSensitive: query.isCaseSensitive
2939
- }),
2940
- buildPropertySimpleValueTextCandidateBranch({
2941
- termExpression,
2942
- isCaseSensitive: query.isCaseSensitive
2943
- }),
2944
- buildPropertyStringCandidateBranch({
2945
- termExpression,
2946
- isCaseSensitive: query.isCaseSensitive,
2947
- language: query.language
2948
- })
2949
- ]),
2950
- buildTokenPredicate: (termsExpression) => buildOrPredicate([buildPropertyRawValueOrTextTokenPredicate({
2951
- termsExpression,
2952
- isCaseSensitive: query.isCaseSensitive
2953
- }), buildPropertyContentTokenPredicate({
2954
- termsExpression,
2955
- isCaseSensitive: query.isCaseSensitive,
2956
- language: query.language,
2957
- excludeInherited: true
2958
- })])
2959
- });
2960
2878
  }
2961
2879
  /**
2962
2880
  * Build a property predicate for an XQuery string.
2963
2881
  */
2964
2882
  function buildPropertyClause(params) {
2965
- const { query, version, queryKey } = params;
2883
+ const { query, version, queryKey, allowCtsPredicates } = params;
2966
2884
  const predicateParts = [];
2967
2885
  const declarations = [];
2968
2886
  const propertyVariable = query.propertyVariable;
2969
2887
  let candidateQueryVar = null;
2970
2888
  if (propertyVariable != null) predicateParts.push(buildPropertyLabelPredicate(propertyVariable));
2971
- if (query.dataType === "date" || query.dataType === "dateTime") if ("value" in query && query.value != null) {
2972
- const compiledScalarClause = buildPropertyRawValueOrTextMatchClause({
2889
+ if (query.dataType === "all") {
2890
+ const compiledAllClause = buildPropertyAllValueClause(query);
2891
+ declarations.push(...compiledAllClause.declarations);
2892
+ predicateParts.push(compiledAllClause.predicate);
2893
+ candidateQueryVar = compiledAllClause.candidateQueryVar;
2894
+ } else if (query.dataType === "date" || query.dataType === "dateTime") if ("value" in query && query.value != null) {
2895
+ const compiledScalarClause = buildPropertyScalarValueClause({
2973
2896
  value: query.value,
2974
2897
  matchMode: query.matchMode,
2975
2898
  isCaseSensitive: query.isCaseSensitive,
2976
2899
  version,
2977
- queryKey
2900
+ queryKey,
2901
+ allowCtsPredicates
2978
2902
  });
2979
2903
  declarations.push(...compiledScalarClause.declarations);
2980
2904
  predicateParts.push(compiledScalarClause.predicate);
@@ -2986,31 +2910,21 @@ function buildPropertyClause(params) {
2986
2910
  else if (query.value != null) switch (query.dataType) {
2987
2911
  case "IDREF":
2988
2912
  predicateParts.push(buildPropertyValueAttributePredicate({
2989
- value: query.value,
2913
+ propertyValue: query.value,
2990
2914
  attributeName: "uuid"
2991
2915
  }));
2992
2916
  break;
2993
- case "all": {
2994
- const compiledAllClause = buildPropertyAllValueClause({
2995
- query,
2996
- version,
2997
- queryKey
2998
- });
2999
- declarations.push(...compiledAllClause.declarations);
3000
- predicateParts.push(compiledAllClause.predicate);
3001
- candidateQueryVar = compiledAllClause.candidateQueryVar;
3002
- break;
3003
- }
3004
2917
  case "integer":
3005
2918
  case "decimal":
3006
2919
  case "time":
3007
2920
  case "boolean": {
3008
- const compiledScalarClause = buildPropertyRawValueOrTextMatchClause({
2921
+ const compiledScalarClause = buildPropertyScalarValueClause({
3009
2922
  value: query.value,
3010
2923
  matchMode: query.matchMode,
3011
2924
  isCaseSensitive: query.isCaseSensitive,
3012
2925
  version,
3013
- queryKey
2926
+ queryKey,
2927
+ allowCtsPredicates
3014
2928
  });
3015
2929
  declarations.push(...compiledScalarClause.declarations);
3016
2930
  predicateParts.push(compiledScalarClause.predicate);
@@ -3021,7 +2935,8 @@ function buildPropertyClause(params) {
3021
2935
  const compiledStringClause = buildPropertyStringValueClause({
3022
2936
  query,
3023
2937
  version,
3024
- queryKey
2938
+ queryKey,
2939
+ allowCtsPredicates
3025
2940
  });
3026
2941
  declarations.push(...compiledStringClause.declarations);
3027
2942
  predicateParts.push(compiledStringClause.predicate);
@@ -3039,38 +2954,94 @@ function buildPropertyClause(params) {
3039
2954
  * Build a query predicate for an XQuery string.
3040
2955
  */
3041
2956
  function buildQueryClause(params) {
3042
- const { query, version, queryKey } = params;
2957
+ const { query, version, queryKey, allowCtsPredicates } = params;
3043
2958
  switch (query.target) {
3044
2959
  case "string": return buildItemStringSearchClause({
3045
2960
  query,
3046
2961
  version,
3047
- queryKey
2962
+ queryKey,
2963
+ allowCtsPredicates
3048
2964
  });
3049
- case "title":
3050
- case "description":
3051
- case "periods":
3052
- case "bibliography":
3053
- case "image": {
3054
- const config = getContentTargetConfig(query.target);
3055
- return buildStringMatchClause({
3056
- contentNodesExpression: config.getContentNodesExpression(query.language),
3057
- value: query.value,
3058
- matchMode: query.matchMode,
2965
+ case "title": return buildStringMatchClause({
2966
+ contentNodesExpression: `identification/label/content[@xml:lang="${query.language}"]`,
2967
+ value: query.value,
2968
+ matchMode: query.matchMode,
2969
+ isCaseSensitive: query.isCaseSensitive,
2970
+ version,
2971
+ queryKey,
2972
+ allowCtsPredicates,
2973
+ buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
2974
+ containerElementName: "identification",
2975
+ termExpression,
3059
2976
  isCaseSensitive: query.isCaseSensitive,
3060
- version,
3061
- queryKey,
3062
- buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
3063
- containerElementName: config.containerElementName,
3064
- termExpression,
3065
- isCaseSensitive: query.isCaseSensitive,
3066
- language: query.language
3067
- })
3068
- });
3069
- }
2977
+ language: query.language
2978
+ })
2979
+ });
2980
+ case "description": return buildStringMatchClause({
2981
+ contentNodesExpression: `description/content[@xml:lang="${query.language}"]`,
2982
+ value: query.value,
2983
+ matchMode: query.matchMode,
2984
+ isCaseSensitive: query.isCaseSensitive,
2985
+ version,
2986
+ queryKey,
2987
+ allowCtsPredicates,
2988
+ buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
2989
+ containerElementName: "description",
2990
+ termExpression,
2991
+ isCaseSensitive: query.isCaseSensitive,
2992
+ language: query.language
2993
+ })
2994
+ });
2995
+ case "periods": return buildStringMatchClause({
2996
+ contentNodesExpression: `periods/period/identification/label/content[@xml:lang="${query.language}"]`,
2997
+ value: query.value,
2998
+ matchMode: query.matchMode,
2999
+ isCaseSensitive: query.isCaseSensitive,
3000
+ version,
3001
+ queryKey,
3002
+ allowCtsPredicates,
3003
+ buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
3004
+ containerElementName: "period",
3005
+ termExpression,
3006
+ isCaseSensitive: query.isCaseSensitive,
3007
+ language: query.language
3008
+ })
3009
+ });
3010
+ case "bibliography": return buildStringMatchClause({
3011
+ contentNodesExpression: `bibliographies/bibliography/identification/label/content[@xml:lang="${query.language}"]`,
3012
+ value: query.value,
3013
+ matchMode: query.matchMode,
3014
+ isCaseSensitive: query.isCaseSensitive,
3015
+ version,
3016
+ queryKey,
3017
+ allowCtsPredicates,
3018
+ buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
3019
+ containerElementName: "bibliography",
3020
+ termExpression,
3021
+ isCaseSensitive: query.isCaseSensitive,
3022
+ language: query.language
3023
+ })
3024
+ });
3025
+ case "image": return buildStringMatchClause({
3026
+ contentNodesExpression: `image/identification/label/content[@xml:lang="${query.language}"]`,
3027
+ value: query.value,
3028
+ matchMode: query.matchMode,
3029
+ isCaseSensitive: query.isCaseSensitive,
3030
+ version,
3031
+ queryKey,
3032
+ allowCtsPredicates,
3033
+ buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
3034
+ containerElementName: "image",
3035
+ termExpression,
3036
+ isCaseSensitive: query.isCaseSensitive,
3037
+ language: query.language
3038
+ })
3039
+ });
3070
3040
  case "property": return buildPropertyClause({
3071
3041
  query,
3072
3042
  version,
3073
- queryKey
3043
+ queryKey,
3044
+ allowCtsPredicates
3074
3045
  });
3075
3046
  }
3076
3047
  }
@@ -3078,11 +3049,12 @@ function buildQueryClause(params) {
3078
3049
  * Build a boolean query clause for an XQuery string.
3079
3050
  */
3080
3051
  function buildBooleanQueryNode(params) {
3081
- const { query, version, queryKey } = params;
3052
+ const { query, version, queryKey, allowCtsPredicates } = params;
3082
3053
  const compiledQueryClause = buildQueryClause({
3083
3054
  query,
3084
3055
  version,
3085
- queryKey
3056
+ queryKey,
3057
+ allowCtsPredicates
3086
3058
  });
3087
3059
  const baseClause = `(${compiledQueryClause.predicate})`;
3088
3060
  return {
@@ -3092,11 +3064,12 @@ function buildBooleanQueryNode(params) {
3092
3064
  };
3093
3065
  }
3094
3066
  function buildQueryNode(params) {
3095
- const { query, version, queryKey } = params;
3067
+ const { query, version, queryKey, allowCtsPredicates } = params;
3096
3068
  if (isQueryLeaf(query)) return buildBooleanQueryNode({
3097
3069
  query,
3098
3070
  version,
3099
- queryKey
3071
+ queryKey,
3072
+ allowCtsPredicates
3100
3073
  });
3101
3074
  const groupQueries = getQueryGroupChildren(query);
3102
3075
  const optimizedIncludesGroupQueries = getCompatibleIncludesGroupLeaves({
@@ -3113,10 +3086,11 @@ function buildQueryNode(params) {
3113
3086
  return buildQueryNode({
3114
3087
  query: onlyQuery,
3115
3088
  version,
3116
- queryKey
3089
+ queryKey,
3090
+ allowCtsPredicates
3117
3091
  });
3118
3092
  }
3119
- if (optimizedIncludesGroupQueries != null) {
3093
+ if (allowCtsPredicates && optimizedIncludesGroupQueries != null) {
3120
3094
  const compiledClause = buildIncludesGroupClause({
3121
3095
  queries: optimizedIncludesGroupQueries,
3122
3096
  queryKey
@@ -3129,20 +3103,29 @@ function buildQueryNode(params) {
3129
3103
  }
3130
3104
  const declarations = [];
3131
3105
  const predicates = [];
3132
- const candidateQueryVars = [];
3133
- for (const [groupIndex, groupQuery] of groupQueries.entries()) {
3134
- const compiledQueryNode = buildQueryNode({
3135
- query: groupQuery,
3136
- version,
3137
- queryKey: `${queryKey}_${groupIndex + 1}`
3138
- });
3106
+ const groupOperator = getQueryGroupOperator(query);
3107
+ let compiledChildNodes = groupQueries.map((groupQuery, groupIndex) => buildQueryNode({
3108
+ query: groupQuery,
3109
+ version,
3110
+ queryKey: `${queryKey}_${groupIndex + 1}`,
3111
+ allowCtsPredicates
3112
+ }));
3113
+ if (allowCtsPredicates && groupOperator === "or" && compiledChildNodes.some((node) => node.candidateQueryVars.length === 0)) compiledChildNodes = groupQueries.map((groupQuery, groupIndex) => buildQueryNode({
3114
+ query: groupQuery,
3115
+ version,
3116
+ queryKey: `${queryKey}_${groupIndex + 1}`,
3117
+ allowCtsPredicates: false
3118
+ }));
3119
+ const candidateQueryVarsByChild = [];
3120
+ for (const compiledQueryNode of compiledChildNodes) {
3139
3121
  declarations.push(...compiledQueryNode.declarations);
3140
3122
  predicates.push(compiledQueryNode.predicate);
3141
- candidateQueryVars.push(...compiledQueryNode.candidateQueryVars);
3123
+ candidateQueryVarsByChild.push(compiledQueryNode.candidateQueryVars);
3142
3124
  }
3125
+ const candidateQueryVars = groupOperator === "and" ? candidateQueryVarsByChild.flat() : candidateQueryVarsByChild.every((vars) => vars.length > 0) ? candidateQueryVarsByChild.flat() : [];
3143
3126
  return {
3144
3127
  declarations,
3145
- predicate: getQueryGroupOperator(query) === "and" ? buildAndPredicate(predicates) : buildOrPredicate(predicates),
3128
+ predicate: groupOperator === "and" ? buildAndPredicate(predicates) : buildOrPredicate(predicates),
3146
3129
  candidateQueryVars
3147
3130
  };
3148
3131
  }
@@ -3155,7 +3138,8 @@ function buildQueryPlan(params) {
3155
3138
  const compiledQueryNode = buildQueryNode({
3156
3139
  query: queries,
3157
3140
  version,
3158
- queryKey: "1"
3141
+ queryKey: "1",
3142
+ allowCtsPredicates: true
3159
3143
  });
3160
3144
  if (compiledQueryNode.declarations.length > 0) declarations.push(`let ${CTS_INCLUDES_STOP_WORDS_VAR} := (${CTS_INCLUDES_STOP_WORDS.map((stopWord) => stringLiteral(stopWord)).join(", ")})`, ...compiledQueryNode.declarations);
3161
3145
  predicate = compiledQueryNode.predicate;
@@ -3336,7 +3320,7 @@ async function fetchSetItems(params, itemCategories, options) {
3336
3320
  else response = await (options?.fetch ?? fetch)(`https://ochre.lib.uchicago.edu/ochre?xquery=${encodeURIComponent(xquery)}&format=json&lang="*"`);
3337
3321
  if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`);
3338
3322
  const data = await response.json();
3339
- if (Array.isArray(data.result) || Object.keys(data.result).length === 0) throw new Error("No items found");
3323
+ if (Array.isArray(data.result) || Object.keys(data.result).length === 0) throw new Error("Invalid OCHRE API response");
3340
3324
  if (itemCategories != null) {
3341
3325
  const itemCategoriesSet = new Set(Object.keys(data.result.ochre.items));
3342
3326
  const missingCategories = itemCategories.filter((category) => !itemCategoriesSet.has(category));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ochre-sdk",
3
- "version": "0.21.6",
3
+ "version": "0.21.7",
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",
@@ -46,8 +46,8 @@
46
46
  "zod": "^4.3.6"
47
47
  },
48
48
  "devDependencies": {
49
- "@antfu/eslint-config": "^7.7.3",
50
- "@types/node": "^24.12.0",
49
+ "@antfu/eslint-config": "^8.0.0",
50
+ "@types/node": "^24.12.2",
51
51
  "bumpp": "^11.0.1",
52
52
  "eslint": "^10.1.0",
53
53
  "prettier": "^3.8.1",