ochre-sdk 0.22.2 → 0.22.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.mjs +422 -1032
  2. package/package.json +4 -2
package/dist/index.mjs CHANGED
@@ -2158,7 +2158,7 @@ async function fetchItem(uuid, category, itemCategories, options) {
2158
2158
  }
2159
2159
  //#endregion
2160
2160
  //#region src/utils/query.ts
2161
- const CTS_INCLUDES_STOP_WORDS = [
2161
+ const CTS_INCLUDES_STOP_WORDS = new Set([
2162
2162
  "and",
2163
2163
  "at",
2164
2164
  "in",
@@ -2166,601 +2166,415 @@ const CTS_INCLUDES_STOP_WORDS = [
2166
2166
  "of",
2167
2167
  "the",
2168
2168
  "to"
2169
- ];
2170
- const CTS_INCLUDES_STOP_WORDS_VAR = "$ctsIncludesStopWords";
2169
+ ]);
2171
2170
  const CTS_INCLUDES_TOKEN_SPLIT_REGEX = /\W+/u;
2171
+ const CONTENT_TARGET_CONTAINER_ELEMENTS = {
2172
+ title: "identification",
2173
+ description: "description",
2174
+ image: "image",
2175
+ periods: "period",
2176
+ bibliography: "bibliography"
2177
+ };
2172
2178
  function tokenizeIncludesSearchValue(params) {
2173
2179
  const { value, isCaseSensitive } = params;
2174
2180
  const rawTerms = (isCaseSensitive ? value : value.toLowerCase()).split(CTS_INCLUDES_TOKEN_SPLIT_REGEX);
2175
2181
  const terms = [];
2176
2182
  for (const term of rawTerms) {
2177
2183
  const normalizedTerm = term.toLowerCase();
2178
- if (normalizedTerm !== "" && !CTS_INCLUDES_STOP_WORDS.includes(normalizedTerm)) terms.push(term);
2184
+ if (normalizedTerm !== "" && !CTS_INCLUDES_STOP_WORDS.has(normalizedTerm)) terms.push(term);
2179
2185
  }
2180
2186
  return terms;
2181
2187
  }
2182
- function buildFlattenedContentValuesExpression(contentNodesExpression) {
2183
- return `for $content in ${contentNodesExpression}
2184
- return string-join($content//text(), "")`;
2185
- }
2186
- function buildSearchableContentNodesExpression(contentNodesExpression) {
2187
- return `for $content in ${contentNodesExpression}
2188
- return (
2189
- $content//string[not(ancestor::string)],
2190
- $content[not(.//string)]
2191
- )`;
2192
- }
2193
- function buildCombinedSearchableContentNodesExpression(contentNodesExpressions) {
2194
- const searchableExpressions = [];
2195
- for (const contentNodesExpression of contentNodesExpressions) searchableExpressions.push(buildSearchableContentNodesExpression(contentNodesExpression));
2196
- if (searchableExpressions.length === 0) return "()";
2197
- if (searchableExpressions.length === 1) return searchableExpressions[0] ?? "()";
2198
- return `(${searchableExpressions.join(", ")})`;
2199
- }
2200
- function buildAttributeValuesExpression(params) {
2201
- const { nodeExpression, attributeName } = params;
2202
- return `for $node in ${nodeExpression}[@${attributeName}]
2203
- return string($node/@${attributeName})`;
2204
- }
2205
- function buildNotesContentNodesExpression(language) {
2206
- return `notes/note/content[@xml:lang="${language}"]`;
2207
- }
2208
- function buildNotesTitleValuesExpression(language) {
2209
- return buildAttributeValuesExpression({
2210
- nodeExpression: buildNotesContentNodesExpression(language),
2211
- attributeName: "title"
2212
- });
2213
- }
2214
- function buildNotesValueExpressions(language) {
2215
- return [buildFlattenedContentValuesExpression(buildNotesContentNodesExpression(language)), buildNotesTitleValuesExpression(language)];
2216
- }
2217
- function buildPropertyValueNodesExpression(params) {
2218
- const predicates = [];
2219
- if (params?.excludeInherited) predicates.push(`not(@inherited="true")`);
2220
- for (const excludedDataType of params?.excludedDataTypes ?? []) predicates.push(`not(@dataType=${stringLiteral(excludedDataType)})`);
2221
- if (predicates.length === 0) return "value";
2222
- return `value[${predicates.join(" and ")}]`;
2223
- }
2224
- function buildPropertyValueContentNodesExpression(params) {
2225
- const { language, excludeInherited, excludedDataTypes } = params;
2226
- return `${buildPropertyValueNodesExpression({
2227
- excludeInherited,
2228
- excludedDataTypes
2229
- })}/content[@xml:lang="${language}"]`;
2230
- }
2231
- function buildPropertyValueContentValuesExpression(params) {
2232
- return buildFlattenedContentValuesExpression(buildPropertyValueContentNodesExpression(params));
2233
- }
2234
- function buildPropertyValueRawValuesExpression(params) {
2235
- return `for $value in ${buildPropertyValueNodesExpression(params)}[@rawValue]
2236
- return string($value/@rawValue)`;
2237
- }
2238
- function buildPropertyValueDirectTextValuesExpression(params) {
2239
- return `for $value in ${buildPropertyValueNodesExpression(params)}
2240
- let $candidate := string-join($value/text(), "")
2241
- where normalize-space($candidate) != ""
2242
- return $candidate`;
2243
- }
2244
- function buildPropertyScalarValueExpressions() {
2245
- return [buildPropertyValueRawValuesExpression(), buildPropertyValueDirectTextValuesExpression()];
2246
- }
2247
- function buildAllPropertyValueExpressions(language) {
2248
- const params = { excludedDataTypes: ["IDREF"] };
2249
- return [
2250
- buildPropertyValueRawValuesExpression(params),
2251
- buildPropertyValueContentValuesExpression({
2252
- language,
2253
- ...params
2254
- }),
2255
- buildPropertyValueDirectTextValuesExpression(params)
2256
- ];
2257
- }
2258
- /**
2259
- * Build a string match predicate for an XQuery string.
2260
- */
2261
- function buildRawStringMatchPredicate(params) {
2262
- const { valueExpression, value, matchMode, isCaseSensitive } = params;
2263
- const comparedValueLiteral = stringLiteral(isCaseSensitive ? value : value.toLowerCase());
2264
- const candidateVar = "$candidate";
2265
- const comparedCandidate = isCaseSensitive ? candidateVar : `lower-case(${candidateVar})`;
2266
- if (matchMode === "includes") return `exists(
2267
- for ${candidateVar} in (${valueExpression})
2268
- where contains(${comparedCandidate}, ${comparedValueLiteral})
2269
- return ${candidateVar}
2270
- )`;
2271
- return `exists(
2272
- for ${candidateVar} in (${valueExpression})
2273
- where ${comparedCandidate} = ${comparedValueLiteral}
2274
- return ${candidateVar}
2275
- )`;
2276
- }
2277
- /**
2278
- * Build a combined raw string match predicate for multiple paths.
2279
- */
2280
- function buildCombinedRawStringMatchPredicate(params) {
2281
- const { valueExpressions, value, matchMode, isCaseSensitive } = params;
2282
- const predicates = [];
2283
- for (const valueExpression of valueExpressions) predicates.push(`(${buildRawStringMatchPredicate({
2284
- valueExpression,
2285
- value,
2286
- matchMode,
2287
- isCaseSensitive
2288
- })})`);
2289
- return buildOrPredicate(predicates);
2290
- }
2291
- /**
2292
- * Build CTS word-query options for API v2 includes search.
2293
- */
2294
- function buildCtsQueryOptionsExpression(params) {
2295
- const { isCaseSensitive, isWildcarded } = params;
2296
- const options = [
2188
+ function buildCtsMatchOptionsExpression(params) {
2189
+ const { isCaseSensitive } = params;
2190
+ return `(${[
2297
2191
  isCaseSensitive ? "case-sensitive" : "case-insensitive",
2298
2192
  "diacritic-insensitive",
2299
- "punctuation-insensitive"
2300
- ];
2301
- if (isWildcarded) options.push("wildcarded");
2302
- return `(${options.map((option) => stringLiteral(option)).join(", ")})`;
2303
- }
2304
- function buildWildcardedTermExpression(termExpression) {
2305
- return `fn:concat("*", ${termExpression}, "*")`;
2193
+ "punctuation-insensitive",
2194
+ "whitespace-insensitive",
2195
+ "unstemmed",
2196
+ "unwildcarded"
2197
+ ].map((option) => stringLiteral(option)).join(", ")})`;
2306
2198
  }
2307
- /**
2308
- * Build a CTS word-query expression for an XQuery term.
2309
- */
2310
2199
  function buildCtsWordQueryExpression(params) {
2311
- const { termExpression, isCaseSensitive, isWildcarded } = params;
2312
- return `cts:word-query(${isWildcarded ? buildWildcardedTermExpression(termExpression) : termExpression}, ${buildCtsQueryOptionsExpression({
2313
- isCaseSensitive,
2314
- isWildcarded
2200
+ const { value, matchMode, isCaseSensitive } = params;
2201
+ return `cts:word-query(${stringLiteral(value)}, ${buildCtsMatchOptionsExpression({
2202
+ matchMode,
2203
+ isCaseSensitive
2315
2204
  })})`;
2316
2205
  }
2317
2206
  function buildCtsElementWordQueryExpression(params) {
2318
- const { elementName, termExpression, isCaseSensitive, isWildcarded } = params;
2319
- return `cts:element-word-query(xs:QName("${elementName}"), ${isWildcarded ? buildWildcardedTermExpression(termExpression) : termExpression}, ${buildCtsQueryOptionsExpression({
2320
- isCaseSensitive,
2321
- isWildcarded
2207
+ const { elementName, value, matchMode, isCaseSensitive } = params;
2208
+ return `cts:element-word-query(xs:QName("${elementName}"), ${stringLiteral(value)}, ${buildCtsMatchOptionsExpression({
2209
+ matchMode,
2210
+ isCaseSensitive
2322
2211
  })})`;
2323
2212
  }
2324
2213
  function buildCtsElementAttributeWordQueryExpression(params) {
2325
- const { elementName, attributeName, termExpression, isCaseSensitive, isWildcarded } = params;
2326
- return `cts:element-attribute-word-query(xs:QName("${elementName}"), xs:QName("${attributeName}"), ${isWildcarded ? buildWildcardedTermExpression(termExpression) : termExpression}, ${buildCtsQueryOptionsExpression({
2327
- isCaseSensitive,
2328
- isWildcarded
2214
+ const { elementName, attributeName, value, matchMode, isCaseSensitive } = params;
2215
+ return `cts:element-attribute-word-query(xs:QName("${elementName}"), xs:QName("${attributeName}"), ${stringLiteral(value)}, ${buildCtsMatchOptionsExpression({
2216
+ matchMode,
2217
+ isCaseSensitive
2329
2218
  })})`;
2330
2219
  }
2331
- /**
2332
- * Build tokenized search declarations for CTS-backed queries.
2333
- */
2334
- function buildTokenizedSearchDeclarations(params) {
2335
- const { value, isCaseSensitive, queryKey } = params;
2336
- const searchStringVar = `$query${queryKey}SearchString`;
2337
- const rawTermsVar = `$query${queryKey}RawTerms`;
2338
- const termsVar = `$query${queryKey}Terms`;
2339
- const tokenSourceExpression = isCaseSensitive ? searchStringVar : `fn:lower-case(${searchStringVar})`;
2340
- return {
2341
- declarations: [
2342
- `let ${searchStringVar} := ${stringLiteral(value)}`,
2343
- String.raw`let ${rawTermsVar} := fn:tokenize(${tokenSourceExpression}, "\W+")`,
2344
- `let ${termsVar} :=
2345
- for $term in ${rawTermsVar}
2346
- let $normalizedTerm := fn:lower-case($term)
2347
- where $normalizedTerm ne "" and not($normalizedTerm = ${CTS_INCLUDES_STOP_WORDS_VAR})
2348
- return $term`
2349
- ],
2350
- termsVar
2351
- };
2220
+ function buildCtsElementValueQueryExpression(params) {
2221
+ const { elementName, value, isCaseSensitive } = params;
2222
+ return `cts:element-value-query(xs:QName("${elementName}"), ${stringLiteral(value)}, ${buildCtsMatchOptionsExpression({
2223
+ matchMode: "exact",
2224
+ isCaseSensitive
2225
+ })})`;
2352
2226
  }
2353
- function buildCtsCandidateQuery(params) {
2354
- const { value, isCaseSensitive, queryKey, buildCandidateTermQuery } = params;
2355
- const tokenizedSearchDeclarations = buildTokenizedSearchDeclarations({
2356
- value,
2357
- isCaseSensitive,
2358
- queryKey
2359
- });
2360
- const termQueriesVar = `$query${queryKey}TermQueries`;
2361
- const candidateQueryVar = `$query${queryKey}CandidateQuery`;
2362
- if (tokenizeIncludesSearchValue({
2363
- value,
2227
+ function buildCtsElementAttributeValueQueryExpression(params) {
2228
+ const { elementName, attributeName, value, isCaseSensitive } = params;
2229
+ return `cts:element-attribute-value-query(xs:QName("${elementName}"), xs:QName("${attributeName}"), ${stringLiteral(value)}, ${buildCtsMatchOptionsExpression({
2230
+ matchMode: "exact",
2364
2231
  isCaseSensitive
2365
- }).length === 0) return {
2366
- declarations: [],
2367
- termsVar: tokenizedSearchDeclarations.termsVar,
2368
- candidateQueryVar: null
2369
- };
2370
- return {
2371
- declarations: [
2372
- ...tokenizedSearchDeclarations.declarations,
2373
- `let ${termQueriesVar} :=
2374
- for $term in ${tokenizedSearchDeclarations.termsVar}
2375
- return ${buildCandidateTermQuery("$term")}`,
2376
- `let ${candidateQueryVar} :=
2377
- if (count(${tokenizedSearchDeclarations.termsVar}) = 1)
2378
- then ${termQueriesVar}[1]
2379
- else if (count(${tokenizedSearchDeclarations.termsVar}) gt 1)
2380
- then cts:and-query(${termQueriesVar})
2381
- else ()`
2382
- ],
2383
- termsVar: tokenizedSearchDeclarations.termsVar,
2384
- candidateQueryVar
2385
- };
2232
+ })})`;
2233
+ }
2234
+ function buildPlainElementAttributeValueQueryExpression(params) {
2235
+ const { elementName, attributeName, value } = params;
2236
+ return `cts:element-attribute-value-query(xs:QName("${elementName}"), xs:QName("${attributeName}"), ${stringLiteral(value)})`;
2386
2237
  }
2387
- function buildCtsIncludesPredicate(params) {
2388
- const { searchableNodesExpression, fallbackValueExpression, value, isCaseSensitive, queryKey, buildCandidateTermQuery } = params;
2389
- const candidateQuery = buildCtsCandidateQuery({
2238
+ function buildSearchableContentTextQueryExpression(params) {
2239
+ const { value, matchMode, isCaseSensitive } = params;
2240
+ if (matchMode === "exact") return buildCtsWordQueryExpression({
2390
2241
  value,
2391
- isCaseSensitive,
2392
- queryKey,
2393
- buildCandidateTermQuery
2242
+ matchMode,
2243
+ isCaseSensitive
2394
2244
  });
2395
- const fallbackPredicate = buildRawStringMatchPredicate({
2396
- valueExpression: fallbackValueExpression,
2245
+ return buildOrCtsQueryExpressionInternal([buildCtsElementWordQueryExpression({
2246
+ elementName: "string",
2397
2247
  value,
2398
- matchMode: "includes",
2248
+ matchMode,
2399
2249
  isCaseSensitive
2400
- });
2401
- if (candidateQuery.candidateQueryVar == null) return {
2402
- declarations: [],
2403
- predicate: fallbackPredicate,
2404
- candidateQueryVar: null
2405
- };
2406
- return {
2407
- declarations: candidateQuery.declarations,
2408
- predicate: `(every $term in ${candidateQuery.termsVar}
2409
- satisfies some $searchNode in (${searchableNodesExpression})
2410
- satisfies cts:contains(
2411
- $searchNode,
2412
- ${buildCtsWordQueryExpression({
2413
- termExpression: "$term",
2414
- isCaseSensitive
2415
- })}
2416
- ))`,
2417
- candidateQueryVar: candidateQuery.candidateQueryVar
2418
- };
2419
- }
2420
- /**
2421
- * Build the raw search paths for item-level string search.
2422
- */
2423
- function buildItemStringSearchPaths(language) {
2424
- return [buildFlattenedContentValuesExpression(`identification/label/content[@xml:lang="${language}"]`), buildFlattenedContentValuesExpression(`properties//property/value[not(@inherited="true")]/content[@xml:lang="${language}"]`)];
2425
- }
2426
- function buildItemStringSearchableNodesExpression(language) {
2427
- return buildCombinedSearchableContentNodesExpression([`identification/label/content[@xml:lang="${language}"]`, `properties//property/value[not(@inherited="true")]/content[@xml:lang="${language}"]`]);
2428
- }
2429
- /**
2430
- * Build the identification branch for an item-level CTS string search.
2431
- */
2432
- function buildItemStringIdentificationBranch(params) {
2433
- const { termExpression, isCaseSensitive, language } = params;
2434
- return `cts:element-query(xs:QName("identification"),
2435
- cts:and-query((
2436
- cts:element-attribute-value-query(xs:QName("content"), xs:QName("xml:lang"), ${stringLiteral(language)}),
2437
- ${buildCtsWordQueryExpression({
2438
- termExpression,
2250
+ }), buildCtsWordQueryExpression({
2251
+ value,
2252
+ matchMode,
2439
2253
  isCaseSensitive
2440
- })}
2441
- ))
2442
- )`;
2254
+ })]);
2443
2255
  }
2444
- /**
2445
- * Build the property value branch for an item-level CTS string search.
2446
- */
2447
- function buildItemStringPropertyValueBranch(params) {
2448
- const { termExpression, isCaseSensitive, language } = params;
2449
- return `cts:element-query(xs:QName("properties"),
2450
- cts:element-query(xs:QName("property"),
2451
- cts:element-query(xs:QName("value"),
2452
- cts:and-query((
2453
- cts:not-query(cts:element-attribute-value-query(xs:QName("value"), xs:QName("inherited"), "true")),
2454
- cts:element-query(xs:QName("content"),
2455
- cts:and-query((
2456
- cts:element-attribute-value-query(xs:QName("content"), xs:QName("xml:lang"), ${stringLiteral(language)}),
2457
- ${buildCtsWordQueryExpression({
2458
- termExpression,
2459
- isCaseSensitive
2460
- })}
2461
- ))
2462
- )
2463
- ))
2464
- )
2465
- )
2466
- )`;
2256
+ function buildNestedElementQuery(elementNames, queryExpression) {
2257
+ let wrappedQueryExpression = queryExpression;
2258
+ for (const elementName of elementNames.toReversed()) wrappedQueryExpression = `cts:element-query(xs:QName("${elementName}"), ${wrappedQueryExpression})`;
2259
+ return wrappedQueryExpression;
2467
2260
  }
2468
- /**
2469
- * Build an item-level CTS string search predicate.
2470
- */
2471
- function buildItemStringSearchClause(params) {
2472
- const { query, version, queryKey, allowCtsPredicates } = params;
2473
- const fallbackPredicate = buildCombinedRawStringMatchPredicate({
2474
- valueExpressions: buildItemStringSearchPaths(query.language),
2475
- value: query.value,
2476
- matchMode: query.matchMode,
2477
- isCaseSensitive: query.isCaseSensitive
2478
- });
2479
- if (!allowCtsPredicates || query.matchMode !== "includes" || version !== 2) return {
2480
- declarations: [],
2481
- predicate: fallbackPredicate,
2482
- candidateQueryVar: null
2483
- };
2484
- return buildCtsIncludesPredicate({
2485
- searchableNodesExpression: buildItemStringSearchableNodesExpression(query.language),
2486
- fallbackValueExpression: `(${buildItemStringSearchPaths(query.language).join(", ")})`,
2487
- value: query.value,
2488
- isCaseSensitive: query.isCaseSensitive,
2489
- queryKey,
2490
- buildCandidateTermQuery: (termExpression) => `cts:or-query((
2491
- ${buildItemStringIdentificationBranch({
2492
- termExpression,
2493
- isCaseSensitive: query.isCaseSensitive,
2494
- language: query.language
2495
- })},
2496
- ${buildItemStringPropertyValueBranch({
2497
- termExpression,
2498
- isCaseSensitive: query.isCaseSensitive,
2499
- language: query.language
2500
- })}
2501
- ))`
2502
- });
2261
+ function buildNotCtsQueryExpression(queryExpression) {
2262
+ return `cts:not-query(${queryExpression})`;
2503
2263
  }
2504
- function buildOrPredicate(predicates) {
2505
- if (predicates.length === 0) return "false()";
2506
- if (predicates.length === 1) return predicates[0] ?? "false()";
2507
- return `(${predicates.join(" or ")})`;
2264
+ function buildAndCtsQueryExpressionInternal(queryExpressions) {
2265
+ if (queryExpressions.length === 0) return "cts:true-query()";
2266
+ if (queryExpressions.length === 1) return queryExpressions[0] ?? "cts:true-query()";
2267
+ return `cts:and-query((${queryExpressions.join(", ")}))`;
2508
2268
  }
2509
- function buildAndPredicate(predicates) {
2510
- if (predicates.length === 0) return "true()";
2511
- if (predicates.length === 1) return predicates[0] ?? "true()";
2512
- return `(${predicates.join(" and ")})`;
2269
+ function buildOrCtsQueryExpressionInternal(queryExpressions) {
2270
+ if (queryExpressions.length === 0) return "cts:false-query()";
2271
+ if (queryExpressions.length === 1) return queryExpressions[0] ?? "cts:false-query()";
2272
+ return `cts:or-query((${queryExpressions.join(", ")}))`;
2513
2273
  }
2514
- /**
2515
- * Build a date/dateTime range predicate for an XQuery string.
2516
- */
2517
- function buildDateRangePredicate(params) {
2518
- const { from, to } = params;
2519
- const conditions = [];
2520
- if (from != null) conditions.push(`(value/@rawValue ge ${stringLiteral(from)})`);
2521
- if (to != null) conditions.push(`(value/@rawValue le ${stringLiteral(to)})`);
2522
- return buildAndPredicate(conditions);
2274
+ function buildAndCtsQueryExpression(queryExpressions) {
2275
+ if (queryExpressions.length === 0) return null;
2276
+ return buildAndCtsQueryExpressionInternal(queryExpressions);
2523
2277
  }
2524
- /**
2525
- * Build a property label predicate for an XQuery string.
2526
- */
2527
- function buildPropertyLabelPredicate(propertyVariable) {
2528
- return `label[@uuid=${stringLiteral(propertyVariable)}]`;
2278
+ function buildContentLanguageQuery(language) {
2279
+ return buildPlainElementAttributeValueQueryExpression({
2280
+ elementName: "content",
2281
+ attributeName: "xml:lang",
2282
+ value: language
2283
+ });
2529
2284
  }
2530
- function buildOrCtsQueryExpression(queries) {
2531
- if (queries.length === 1) return queries[0] ?? "cts:false-query()";
2532
- return `cts:or-query((${queries.join(", ")}))`;
2285
+ function buildPropertyLabelQuery(propertyVariable) {
2286
+ return buildPlainElementAttributeValueQueryExpression({
2287
+ elementName: "label",
2288
+ attributeName: "uuid",
2289
+ value: propertyVariable
2290
+ });
2533
2291
  }
2534
- function isQueryLeaf(query) {
2535
- return "target" in query;
2292
+ function buildValueNotInheritedQuery() {
2293
+ return buildNotCtsQueryExpression(buildPlainElementAttributeValueQueryExpression({
2294
+ elementName: "value",
2295
+ attributeName: "inherited",
2296
+ value: "true"
2297
+ }));
2536
2298
  }
2537
- function getQueryGroupChildren(query) {
2538
- return "and" in query ? query.and : query.or;
2299
+ function buildValueNotIdRefQuery() {
2300
+ return buildNotCtsQueryExpression(buildPlainElementAttributeValueQueryExpression({
2301
+ elementName: "value",
2302
+ attributeName: "dataType",
2303
+ value: "IDREF"
2304
+ }));
2539
2305
  }
2540
- function getQueryGroupOperator(query) {
2541
- return "and" in query ? "and" : "or";
2306
+ function buildValueContentInnerQuery(params) {
2307
+ const { language, value, matchMode, isCaseSensitive } = params;
2308
+ return buildNestedElementQuery(["content"], buildAndCtsQueryExpressionInternal([buildContentLanguageQuery(language), buildSearchableContentTextQueryExpression({
2309
+ value,
2310
+ matchMode,
2311
+ isCaseSensitive
2312
+ })]));
2542
2313
  }
2543
- function buildContentTargetCandidateBranch(params) {
2544
- const { containerElementName, termExpression, isCaseSensitive, language } = params;
2545
- return `cts:element-query(xs:QName("${containerElementName}"),
2546
- cts:and-query((
2547
- cts:element-attribute-value-query(xs:QName("content"), xs:QName("xml:lang"), ${stringLiteral(language)}),
2548
- ${buildCtsWordQueryExpression({
2549
- termExpression,
2314
+ function buildValueDirectTextInnerQuery(params) {
2315
+ const { value, matchMode, isCaseSensitive } = params;
2316
+ const directTextQuery = matchMode === "exact" ? buildCtsElementValueQueryExpression({
2317
+ elementName: "value",
2318
+ value,
2550
2319
  isCaseSensitive
2551
- })}
2552
- ))
2553
- )`;
2554
- }
2555
- function buildPropertyStringCandidateBranch(params) {
2556
- const { termExpression, isCaseSensitive, language } = params;
2557
- return `cts:element-query(xs:QName("properties"),
2558
- cts:element-query(xs:QName("property"),
2559
- cts:element-query(xs:QName("value"),
2560
- cts:and-query((
2561
- cts:not-query(cts:element-attribute-value-query(xs:QName("value"), xs:QName("inherited"), "true")),
2562
- cts:element-query(xs:QName("content"),
2563
- cts:and-query((
2564
- cts:element-attribute-value-query(xs:QName("content"), xs:QName("xml:lang"), ${stringLiteral(language)}),
2565
- ${buildCtsWordQueryExpression({
2566
- termExpression,
2320
+ }) : buildCtsElementWordQueryExpression({
2321
+ elementName: "value",
2322
+ value,
2323
+ matchMode,
2567
2324
  isCaseSensitive
2568
- })}
2569
- ))
2570
- )
2571
- ))
2572
- )
2573
- )
2574
- )`;
2575
- }
2576
- function buildPropertyScalarCandidateBranch(params) {
2577
- const { termExpression, isCaseSensitive } = params;
2578
- return `cts:element-query(xs:QName("properties"),
2579
- cts:element-query(xs:QName("property"),
2580
- cts:or-query((
2581
- ${buildCtsElementAttributeWordQueryExpression({
2325
+ });
2326
+ return buildAndCtsQueryExpressionInternal([buildNotCtsQueryExpression(buildNestedElementQuery(["content"], "cts:true-query()")), directTextQuery]);
2327
+ }
2328
+ function buildValueRawValueInnerQuery(params) {
2329
+ const { value, matchMode, isCaseSensitive } = params;
2330
+ if (matchMode === "exact") return buildCtsElementAttributeValueQueryExpression({
2582
2331
  elementName: "value",
2583
2332
  attributeName: "rawValue",
2584
- termExpression,
2585
- isCaseSensitive,
2586
- isWildcarded: true
2587
- })},
2588
- ${buildCtsElementWordQueryExpression({
2333
+ value,
2334
+ isCaseSensitive
2335
+ });
2336
+ return buildCtsElementAttributeWordQueryExpression({
2589
2337
  elementName: "value",
2590
- termExpression,
2591
- isCaseSensitive,
2592
- isWildcarded: true
2593
- })}
2594
- ))
2595
- )
2596
- )`;
2597
- }
2598
- function buildNotesCandidateBranch(params) {
2599
- const { termExpression, isCaseSensitive, language } = params;
2600
- return `cts:element-query(xs:QName("notes"),
2601
- cts:element-query(xs:QName("note"),
2602
- cts:element-query(xs:QName("content"),
2603
- cts:and-query((
2604
- cts:element-attribute-value-query(xs:QName("content"), xs:QName("xml:lang"), ${stringLiteral(language)}),
2605
- cts:or-query((
2606
- ${buildCtsElementAttributeWordQueryExpression({
2338
+ attributeName: "rawValue",
2339
+ value,
2340
+ matchMode,
2341
+ isCaseSensitive
2342
+ });
2343
+ }
2344
+ function buildNotesQueryExpression(params) {
2345
+ const { value, matchMode, isCaseSensitive, language } = params;
2346
+ return buildNestedElementQuery([
2347
+ "notes",
2348
+ "note",
2349
+ "content"
2350
+ ], buildAndCtsQueryExpressionInternal([buildContentLanguageQuery(language), buildOrCtsQueryExpressionInternal([matchMode === "exact" ? buildCtsElementAttributeValueQueryExpression({
2607
2351
  elementName: "content",
2608
2352
  attributeName: "title",
2609
- termExpression,
2353
+ value,
2610
2354
  isCaseSensitive
2611
- })},
2612
- ${buildCtsWordQueryExpression({
2613
- termExpression,
2355
+ }) : buildCtsElementAttributeWordQueryExpression({
2356
+ elementName: "content",
2357
+ attributeName: "title",
2358
+ value,
2359
+ matchMode,
2614
2360
  isCaseSensitive
2615
- })}
2616
- ))
2617
- ))
2618
- )
2619
- )
2620
- )`;
2361
+ }), buildSearchableContentTextQueryExpression({
2362
+ value,
2363
+ matchMode,
2364
+ isCaseSensitive
2365
+ })])]));
2621
2366
  }
2622
- function getGroupableIncludesValue(query) {
2367
+ function buildContentTargetQueryExpression(params) {
2368
+ const { target, value, matchMode, isCaseSensitive, language } = params;
2369
+ const containerElement = CONTENT_TARGET_CONTAINER_ELEMENTS[target];
2370
+ return buildNestedElementQuery([containerElement], buildAndCtsQueryExpressionInternal([buildContentLanguageQuery(language), buildSearchableContentTextQueryExpression({
2371
+ value,
2372
+ matchMode,
2373
+ isCaseSensitive
2374
+ })]));
2375
+ }
2376
+ function buildPropertyQueryExpression(params) {
2377
+ const { propertyVariable, queryExpression } = params;
2378
+ const propertyQueryExpressions = [queryExpression];
2379
+ if (propertyVariable != null) propertyQueryExpressions.unshift(buildPropertyLabelQuery(propertyVariable));
2380
+ return buildNestedElementQuery(["properties", "property"], buildAndCtsQueryExpressionInternal(propertyQueryExpressions));
2381
+ }
2382
+ function buildPropertyPresenceQueryExpression(params) {
2383
+ return buildPropertyQueryExpression({
2384
+ propertyVariable: params.propertyVariable,
2385
+ queryExpression: "cts:true-query()"
2386
+ });
2387
+ }
2388
+ function buildPropertyStringQueryExpression(params) {
2389
+ const { propertyVariable, value, matchMode, isCaseSensitive, language } = params;
2390
+ return buildPropertyQueryExpression({
2391
+ propertyVariable,
2392
+ queryExpression: buildNestedElementQuery(["value"], buildAndCtsQueryExpressionInternal([buildValueNotInheritedQuery(), buildValueContentInnerQuery({
2393
+ language,
2394
+ value,
2395
+ matchMode,
2396
+ isCaseSensitive
2397
+ })]))
2398
+ });
2399
+ }
2400
+ function buildPropertyScalarQueryExpression(params) {
2401
+ const { propertyVariable, value, matchMode, isCaseSensitive } = params;
2402
+ return buildPropertyQueryExpression({
2403
+ propertyVariable,
2404
+ queryExpression: buildNestedElementQuery(["value"], buildOrCtsQueryExpressionInternal([buildValueRawValueInnerQuery({
2405
+ value,
2406
+ matchMode,
2407
+ isCaseSensitive
2408
+ }), buildValueDirectTextInnerQuery({
2409
+ value,
2410
+ matchMode,
2411
+ isCaseSensitive
2412
+ })]))
2413
+ });
2414
+ }
2415
+ function buildPropertyAllQueryExpression(params) {
2416
+ const { query, value, matchMode } = params;
2417
+ return buildPropertyQueryExpression({
2418
+ propertyVariable: query.propertyVariable,
2419
+ queryExpression: buildNestedElementQuery(["value"], buildAndCtsQueryExpressionInternal([buildValueNotIdRefQuery(), buildCtsWordQueryExpression({
2420
+ value,
2421
+ matchMode,
2422
+ isCaseSensitive: query.isCaseSensitive
2423
+ })]))
2424
+ });
2425
+ }
2426
+ function buildPropertyIdRefQueryExpression(params) {
2427
+ const { propertyVariable, value } = params;
2428
+ return buildPropertyQueryExpression({
2429
+ propertyVariable,
2430
+ queryExpression: buildNestedElementQuery(["value"], buildPlainElementAttributeValueQueryExpression({
2431
+ elementName: "value",
2432
+ attributeName: "uuid",
2433
+ value
2434
+ }))
2435
+ });
2436
+ }
2437
+ function buildPropertyDateRangeQueryExpression(query) {
2438
+ const rangeQueryExpressions = [];
2439
+ if (query.from != null) rangeQueryExpressions.push(`cts:element-attribute-range-query(xs:QName("value"), xs:QName("rawValue"), ">=", ${stringLiteral(query.from)})`);
2440
+ if (query.to != null) rangeQueryExpressions.push(`cts:element-attribute-range-query(xs:QName("value"), xs:QName("rawValue"), "<=", ${stringLiteral(query.to)})`);
2441
+ return buildPropertyQueryExpression({
2442
+ propertyVariable: query.propertyVariable,
2443
+ queryExpression: buildNestedElementQuery(["value"], buildAndCtsQueryExpressionInternal(rangeQueryExpressions))
2444
+ });
2445
+ }
2446
+ function buildItemStringQueryExpression(params) {
2447
+ const { value, matchMode, isCaseSensitive, language } = params;
2448
+ return buildOrCtsQueryExpressionInternal([buildContentTargetQueryExpression({
2449
+ target: "title",
2450
+ value,
2451
+ matchMode,
2452
+ isCaseSensitive,
2453
+ language
2454
+ }), buildPropertyStringQueryExpression({
2455
+ value,
2456
+ matchMode,
2457
+ isCaseSensitive,
2458
+ language
2459
+ })]);
2460
+ }
2461
+ function getLeafSearchValue(query) {
2623
2462
  switch (query.target) {
2624
2463
  case "string":
2625
2464
  case "title":
2626
2465
  case "description":
2627
2466
  case "image":
2628
- case "notes":
2629
2467
  case "periods":
2630
- case "bibliography": return query.value;
2631
- case "property":
2632
- if (query.dataType !== "string" || query.value == null) return null;
2633
- return query.value;
2468
+ case "bibliography":
2469
+ case "notes": return query.value;
2470
+ case "property": return "value" in query && query.value != null ? query.value : null;
2634
2471
  }
2635
2472
  }
2636
- function isCompatibleIncludesGroupQuery(params) {
2637
- const { query, value, isCaseSensitive, language, version } = params;
2638
- if (version !== 2 || query.matchMode !== "includes" || query.isNegated === true) return false;
2639
- const queryValue = getGroupableIncludesValue(query);
2640
- return queryValue != null && queryValue === value && query.isCaseSensitive === isCaseSensitive && query.language === language;
2641
- }
2642
- function buildContentTargetIncludesGroupMember(params) {
2643
- const { contentNodesExpression, value, isCaseSensitive, language, containerElementName } = params;
2644
- return {
2645
- rawPredicate: buildRawStringMatchPredicate({
2646
- valueExpression: buildFlattenedContentValuesExpression(contentNodesExpression),
2473
+ function buildLeafValueQueryExpression(params) {
2474
+ const { query, value, matchMode } = params;
2475
+ switch (query.target) {
2476
+ case "string": return buildItemStringQueryExpression({
2647
2477
  value,
2648
- matchMode: "includes",
2649
- isCaseSensitive
2650
- }),
2651
- buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
2652
- containerElementName,
2653
- termExpression,
2654
- isCaseSensitive,
2655
- language
2656
- })
2657
- };
2658
- }
2659
- function buildItemStringIncludesGroupMember(query) {
2660
- return {
2661
- rawPredicate: buildCombinedRawStringMatchPredicate({
2662
- valueExpressions: buildItemStringSearchPaths(query.language),
2663
- value: query.value,
2664
- matchMode: "includes",
2665
- isCaseSensitive: query.isCaseSensitive
2666
- }),
2667
- buildCandidateTermQuery: (termExpression) => `cts:or-query((
2668
- ${buildItemStringIdentificationBranch({
2669
- termExpression,
2478
+ matchMode,
2670
2479
  isCaseSensitive: query.isCaseSensitive,
2671
2480
  language: query.language
2672
- })},
2673
- ${buildItemStringPropertyValueBranch({
2674
- termExpression,
2481
+ });
2482
+ case "notes": return buildNotesQueryExpression({
2483
+ value,
2484
+ matchMode,
2675
2485
  isCaseSensitive: query.isCaseSensitive,
2676
2486
  language: query.language
2677
- })}
2678
- ))`
2679
- };
2680
- }
2681
- function buildPropertyStringIncludesGroupMember(query) {
2682
- const propertyVariable = query.propertyVariable;
2683
- const predicateParts = [];
2684
- const valueExpression = buildPropertyValueContentValuesExpression({
2685
- language: query.language,
2686
- excludeInherited: true
2687
- });
2688
- if (propertyVariable != null) predicateParts.push(buildPropertyLabelPredicate(propertyVariable));
2689
- return {
2690
- rawPredicate: buildPropertyPredicateExpression([...predicateParts, buildRawStringMatchPredicate({
2691
- valueExpression,
2692
- value: query.value,
2693
- matchMode: "includes",
2694
- isCaseSensitive: query.isCaseSensitive
2695
- })]),
2696
- buildCandidateTermQuery: (termExpression) => buildPropertyStringCandidateBranch({
2697
- termExpression,
2487
+ });
2488
+ case "title":
2489
+ case "description":
2490
+ case "image":
2491
+ case "periods":
2492
+ case "bibliography": return buildContentTargetQueryExpression({
2493
+ target: query.target,
2494
+ value,
2495
+ matchMode,
2698
2496
  isCaseSensitive: query.isCaseSensitive,
2699
2497
  language: query.language
2700
- })
2701
- };
2498
+ });
2499
+ case "property": switch (query.dataType) {
2500
+ case "all": return buildPropertyAllQueryExpression({
2501
+ query,
2502
+ value,
2503
+ matchMode
2504
+ });
2505
+ case "IDREF": return buildPropertyIdRefQueryExpression({
2506
+ propertyVariable: query.propertyVariable,
2507
+ value
2508
+ });
2509
+ case "string": return buildPropertyStringQueryExpression({
2510
+ propertyVariable: query.propertyVariable,
2511
+ value,
2512
+ matchMode,
2513
+ isCaseSensitive: query.isCaseSensitive,
2514
+ language: query.language
2515
+ });
2516
+ case "integer":
2517
+ case "decimal":
2518
+ case "time":
2519
+ case "boolean":
2520
+ case "date":
2521
+ case "dateTime": return buildPropertyScalarQueryExpression({
2522
+ propertyVariable: query.propertyVariable,
2523
+ value,
2524
+ matchMode,
2525
+ isCaseSensitive: query.isCaseSensitive
2526
+ });
2527
+ }
2528
+ }
2702
2529
  }
2703
- function buildNotesIncludesGroupMember(query) {
2704
- return {
2705
- rawPredicate: buildCombinedRawStringMatchPredicate({
2706
- valueExpressions: buildNotesValueExpressions(query.language),
2707
- value: query.value,
2708
- matchMode: "includes",
2709
- isCaseSensitive: query.isCaseSensitive
2710
- }),
2711
- buildCandidateTermQuery: (termExpression) => buildNotesCandidateBranch({
2712
- termExpression,
2713
- isCaseSensitive: query.isCaseSensitive,
2714
- language: query.language
2715
- })
2716
- };
2530
+ function buildLeafQueryExpression(query) {
2531
+ if (query.target === "property" && query.dataType !== "date" && query.dataType !== "dateTime" && !("value" in query) && query.propertyVariable != null) return buildPropertyPresenceQueryExpression({ propertyVariable: query.propertyVariable });
2532
+ if (query.target === "property" && (query.dataType === "date" || query.dataType === "dateTime") && query.value == null) return buildPropertyDateRangeQueryExpression(query);
2533
+ const searchValue = getLeafSearchValue(query);
2534
+ if (searchValue == null) throw new Error("Missing searchable value for query leaf");
2535
+ if (query.matchMode === "exact") return buildLeafValueQueryExpression({
2536
+ query,
2537
+ value: searchValue,
2538
+ matchMode: "exact"
2539
+ });
2540
+ const terms = tokenizeIncludesSearchValue({
2541
+ value: searchValue,
2542
+ isCaseSensitive: query.isCaseSensitive
2543
+ });
2544
+ if (terms.length === 0) return "cts:false-query()";
2545
+ const termQueryExpressions = [];
2546
+ for (const term of terms) termQueryExpressions.push(buildLeafValueQueryExpression({
2547
+ query,
2548
+ value: term,
2549
+ matchMode: "includes"
2550
+ }));
2551
+ return buildAndCtsQueryExpressionInternal(termQueryExpressions);
2717
2552
  }
2718
- function buildIncludesGroupMember(query) {
2553
+ function getGroupableIncludesValue(query) {
2554
+ if (query.matchMode !== "includes" || query.isNegated === true) return null;
2719
2555
  switch (query.target) {
2720
- case "string": return buildItemStringIncludesGroupMember(query);
2721
- case "title": return buildContentTargetIncludesGroupMember({
2722
- contentNodesExpression: `identification/label/content[@xml:lang="${query.language}"]`,
2723
- value: query.value,
2724
- isCaseSensitive: query.isCaseSensitive,
2725
- language: query.language,
2726
- containerElementName: "identification"
2727
- });
2728
- case "description": return buildContentTargetIncludesGroupMember({
2729
- contentNodesExpression: `description/content[@xml:lang="${query.language}"]`,
2730
- value: query.value,
2731
- isCaseSensitive: query.isCaseSensitive,
2732
- language: query.language,
2733
- containerElementName: "description"
2734
- });
2735
- case "periods": return buildContentTargetIncludesGroupMember({
2736
- contentNodesExpression: `periods/period/identification/label/content[@xml:lang="${query.language}"]`,
2737
- value: query.value,
2738
- isCaseSensitive: query.isCaseSensitive,
2739
- language: query.language,
2740
- containerElementName: "period"
2741
- });
2742
- case "bibliography": return buildContentTargetIncludesGroupMember({
2743
- contentNodesExpression: `bibliographies/bibliography/identification/label/content[@xml:lang="${query.language}"]`,
2744
- value: query.value,
2745
- isCaseSensitive: query.isCaseSensitive,
2746
- language: query.language,
2747
- containerElementName: "bibliography"
2748
- });
2749
- case "image": return buildContentTargetIncludesGroupMember({
2750
- contentNodesExpression: `image/identification/label/content[@xml:lang="${query.language}"]`,
2751
- value: query.value,
2752
- isCaseSensitive: query.isCaseSensitive,
2753
- language: query.language,
2754
- containerElementName: "image"
2755
- });
2756
- case "notes": return buildNotesIncludesGroupMember(query);
2556
+ case "string":
2557
+ case "title":
2558
+ case "description":
2559
+ case "image":
2560
+ case "periods":
2561
+ case "bibliography":
2562
+ case "notes": return query.value;
2757
2563
  case "property":
2758
- if (query.dataType === "all") throw new Error(`Property queries with dataType "all" are not compatible with includes-group optimization`);
2759
- return buildPropertyStringIncludesGroupMember(query);
2564
+ if (!("value" in query) || query.value == null || query.dataType === "IDREF") return null;
2565
+ return query.value;
2760
2566
  }
2761
2567
  }
2762
- function getCompatibleIncludesGroupLeaves(params) {
2763
- const { query, version } = params;
2568
+ function isQueryLeaf(query) {
2569
+ return "target" in query;
2570
+ }
2571
+ function getQueryGroupChildren(query) {
2572
+ return "and" in query ? query.and : query.or;
2573
+ }
2574
+ function getQueryGroupOperator(query) {
2575
+ return "and" in query ? "and" : "or";
2576
+ }
2577
+ function getCompatibleIncludesGroupLeaves(query) {
2764
2578
  if (!("or" in query) || query.or.length <= 1) return null;
2765
2579
  const leafQueries = [];
2766
2580
  for (const childQuery of query.or) {
@@ -2770,482 +2584,64 @@ function getCompatibleIncludesGroupLeaves(params) {
2770
2584
  const firstQuery = leafQueries[0];
2771
2585
  if (firstQuery == null) return null;
2772
2586
  const groupValue = getGroupableIncludesValue(firstQuery);
2773
- if (groupValue == null || !isCompatibleIncludesGroupQuery({
2774
- query: firstQuery,
2775
- value: groupValue,
2776
- isCaseSensitive: firstQuery.isCaseSensitive,
2777
- language: firstQuery.language,
2778
- version
2779
- })) return null;
2780
- for (const leafQuery of leafQueries.slice(1)) if (!isCompatibleIncludesGroupQuery({
2781
- query: leafQuery,
2782
- value: groupValue,
2783
- isCaseSensitive: firstQuery.isCaseSensitive,
2784
- language: firstQuery.language,
2785
- version
2786
- })) return null;
2587
+ if (groupValue == null) return null;
2588
+ for (const leafQuery of leafQueries) if (getGroupableIncludesValue(leafQuery) !== groupValue || leafQuery.isCaseSensitive !== firstQuery.isCaseSensitive || leafQuery.language !== firstQuery.language) return null;
2787
2589
  return leafQueries;
2788
2590
  }
2789
- function buildIncludesGroupClause(params) {
2790
- const { queries, queryKey } = params;
2591
+ function buildIncludesGroupMember(query) {
2592
+ return { buildTermQuery: (term) => buildLeafValueQueryExpression({
2593
+ query,
2594
+ value: term,
2595
+ matchMode: "includes"
2596
+ }) };
2597
+ }
2598
+ function buildIncludesGroupQueryExpression(queries) {
2791
2599
  const firstQuery = queries[0];
2792
2600
  if (firstQuery == null) throw new Error("Cannot build an includes group without queries");
2793
2601
  const groupValue = getGroupableIncludesValue(firstQuery);
2794
2602
  if (groupValue == null) throw new Error("Cannot build an includes group without a search value");
2795
- const members = queries.map((query) => buildIncludesGroupMember(query));
2796
- const tokenizedSearchDeclarations = buildTokenizedSearchDeclarations({
2797
- value: groupValue,
2798
- isCaseSensitive: firstQuery.isCaseSensitive,
2799
- queryKey
2800
- });
2801
- const tokenizedTerms = tokenizeIncludesSearchValue({
2603
+ const terms = tokenizeIncludesSearchValue({
2802
2604
  value: groupValue,
2803
2605
  isCaseSensitive: firstQuery.isCaseSensitive
2804
2606
  });
2805
- const termQueriesVar = `$query${queryKey}TermQueries`;
2806
- const candidateQueryVar = `$query${queryKey}CandidateQuery`;
2807
- if (tokenizedTerms.length === 0) return {
2808
- declarations: [],
2809
- predicate: buildOrPredicate(members.map((member) => member.rawPredicate)),
2810
- candidateQueryVar: null
2811
- };
2812
- return {
2813
- declarations: [
2814
- ...tokenizedSearchDeclarations.declarations,
2815
- `let ${termQueriesVar} :=
2816
- for $term in ${tokenizedSearchDeclarations.termsVar}
2817
- return ${buildOrCtsQueryExpression(members.map((member) => member.buildCandidateTermQuery("$term")))}`,
2818
- `let ${candidateQueryVar} :=
2819
- if (count(${tokenizedSearchDeclarations.termsVar}) = 1)
2820
- then ${termQueriesVar}[1]
2821
- else if (count(${tokenizedSearchDeclarations.termsVar}) gt 1)
2822
- then cts:and-query(${termQueriesVar})
2823
- else ()`
2824
- ],
2825
- predicate: `cts:contains(., ${candidateQueryVar})`,
2826
- candidateQueryVar
2827
- };
2828
- }
2829
- /**
2830
- * Build a string match predicate for an XQuery string.
2831
- */
2832
- function buildStringMatchClause(params) {
2833
- const { contentNodesExpression, value, matchMode, isCaseSensitive, version, queryKey, allowCtsPredicates, buildCandidateTermQuery } = params;
2834
- const valueExpression = buildFlattenedContentValuesExpression(contentNodesExpression);
2835
- if (allowCtsPredicates && matchMode === "includes" && version === 2 && buildCandidateTermQuery != null) return buildCtsIncludesPredicate({
2836
- searchableNodesExpression: buildSearchableContentNodesExpression(contentNodesExpression),
2837
- fallbackValueExpression: valueExpression,
2838
- value,
2839
- isCaseSensitive,
2840
- queryKey,
2841
- buildCandidateTermQuery
2842
- });
2843
- return {
2844
- declarations: [],
2845
- predicate: buildRawStringMatchPredicate({
2846
- valueExpression,
2847
- value,
2848
- matchMode,
2849
- isCaseSensitive
2850
- }),
2851
- candidateQueryVar: null
2852
- };
2853
- }
2854
- function buildCombinedRawStringMatchClause(params) {
2855
- const { valueExpressions, value, matchMode, isCaseSensitive, version, queryKey, allowCtsPredicates, buildCandidateTermQuery } = params;
2856
- const predicate = buildCombinedRawStringMatchPredicate({
2857
- valueExpressions,
2858
- value,
2859
- matchMode,
2860
- isCaseSensitive
2861
- });
2862
- if (allowCtsPredicates && matchMode === "includes" && version === 2 && buildCandidateTermQuery != null) {
2863
- const candidateQuery = buildCtsCandidateQuery({
2864
- value,
2865
- isCaseSensitive,
2866
- queryKey,
2867
- buildCandidateTermQuery
2868
- });
2869
- return {
2870
- declarations: candidateQuery.declarations,
2871
- predicate,
2872
- candidateQueryVar: candidateQuery.candidateQueryVar
2873
- };
2874
- }
2875
- return {
2876
- declarations: [],
2877
- predicate,
2878
- candidateQueryVar: null
2879
- };
2880
- }
2881
- function buildPropertyValueAttributePredicate(params) {
2882
- const { propertyValue, attributeName } = params;
2883
- return `value[@${attributeName}=${stringLiteral(propertyValue)}]`;
2884
- }
2885
- function buildPropertyPredicateExpression(propertyPredicates) {
2886
- let propertyExpression = ".//properties//property";
2887
- for (const propertyPredicate of propertyPredicates) propertyExpression += `[${propertyPredicate}]`;
2888
- return propertyExpression;
2889
- }
2890
- function buildPropertyStringValueClause(params) {
2891
- const { query, version, queryKey, allowCtsPredicates } = params;
2892
- return buildStringMatchClause({
2893
- contentNodesExpression: buildPropertyValueContentNodesExpression({
2894
- language: query.language,
2895
- excludeInherited: allowCtsPredicates && query.matchMode === "includes" && version === 2
2896
- }),
2897
- value: query.value,
2898
- matchMode: query.matchMode,
2899
- isCaseSensitive: query.isCaseSensitive,
2900
- version,
2901
- queryKey,
2902
- allowCtsPredicates,
2903
- buildCandidateTermQuery: (termExpression) => buildPropertyStringCandidateBranch({
2904
- termExpression,
2905
- isCaseSensitive: query.isCaseSensitive,
2906
- language: query.language
2907
- })
2908
- });
2909
- }
2910
- function buildPropertyScalarValueClause(params) {
2911
- const { value, matchMode, isCaseSensitive, version, queryKey, allowCtsPredicates } = params;
2912
- return buildCombinedRawStringMatchClause({
2913
- valueExpressions: buildPropertyScalarValueExpressions(),
2914
- value,
2915
- matchMode,
2916
- isCaseSensitive,
2917
- version,
2918
- queryKey,
2919
- allowCtsPredicates,
2920
- buildCandidateTermQuery: (termExpression) => buildPropertyScalarCandidateBranch({
2921
- termExpression,
2922
- isCaseSensitive
2923
- })
2924
- });
2925
- }
2926
- function buildPropertyAllValueClause(query) {
2927
- return {
2928
- declarations: [],
2929
- predicate: buildCombinedRawStringMatchPredicate({
2930
- valueExpressions: buildAllPropertyValueExpressions(query.language),
2931
- value: query.value,
2932
- matchMode: query.matchMode,
2933
- isCaseSensitive: query.isCaseSensitive
2934
- }),
2935
- candidateQueryVar: null
2936
- };
2937
- }
2938
- function buildNotesValueClause(params) {
2939
- const { query, version, queryKey, allowCtsPredicates } = params;
2940
- return buildCombinedRawStringMatchClause({
2941
- valueExpressions: buildNotesValueExpressions(query.language),
2942
- value: query.value,
2943
- matchMode: query.matchMode,
2944
- isCaseSensitive: query.isCaseSensitive,
2945
- version,
2946
- queryKey,
2947
- allowCtsPredicates,
2948
- buildCandidateTermQuery: (termExpression) => buildNotesCandidateBranch({
2949
- termExpression,
2950
- isCaseSensitive: query.isCaseSensitive,
2951
- language: query.language
2952
- })
2953
- });
2954
- }
2955
- /**
2956
- * Build a property predicate for an XQuery string.
2957
- */
2958
- function buildPropertyClause(params) {
2959
- const { query, version, queryKey, allowCtsPredicates } = params;
2960
- const predicateParts = [];
2961
- const declarations = [];
2962
- const propertyVariable = query.propertyVariable;
2963
- let candidateQueryVar = null;
2964
- if (propertyVariable != null) predicateParts.push(buildPropertyLabelPredicate(propertyVariable));
2965
- if (query.dataType === "all") {
2966
- const compiledAllClause = buildPropertyAllValueClause(query);
2967
- declarations.push(...compiledAllClause.declarations);
2968
- predicateParts.push(compiledAllClause.predicate);
2969
- candidateQueryVar = compiledAllClause.candidateQueryVar;
2970
- } else if (query.dataType === "date" || query.dataType === "dateTime") if ("value" in query && query.value != null) {
2971
- const compiledScalarClause = buildPropertyScalarValueClause({
2972
- value: query.value,
2973
- matchMode: query.matchMode,
2974
- isCaseSensitive: query.isCaseSensitive,
2975
- version,
2976
- queryKey,
2977
- allowCtsPredicates
2978
- });
2979
- declarations.push(...compiledScalarClause.declarations);
2980
- predicateParts.push(compiledScalarClause.predicate);
2981
- candidateQueryVar = compiledScalarClause.candidateQueryVar;
2982
- } else predicateParts.push(buildDateRangePredicate({
2983
- from: query.from,
2984
- to: query.to
2985
- }));
2986
- else if (query.value != null) switch (query.dataType) {
2987
- case "IDREF":
2988
- predicateParts.push(buildPropertyValueAttributePredicate({
2989
- propertyValue: query.value,
2990
- attributeName: "uuid"
2991
- }));
2992
- break;
2993
- case "integer":
2994
- case "decimal":
2995
- case "time":
2996
- case "boolean": {
2997
- const compiledScalarClause = buildPropertyScalarValueClause({
2998
- value: query.value,
2999
- matchMode: query.matchMode,
3000
- isCaseSensitive: query.isCaseSensitive,
3001
- version,
3002
- queryKey,
3003
- allowCtsPredicates
3004
- });
3005
- declarations.push(...compiledScalarClause.declarations);
3006
- predicateParts.push(compiledScalarClause.predicate);
3007
- candidateQueryVar = compiledScalarClause.candidateQueryVar;
3008
- break;
3009
- }
3010
- case "string": {
3011
- const compiledStringClause = buildPropertyStringValueClause({
3012
- query,
3013
- version,
3014
- queryKey,
3015
- allowCtsPredicates
3016
- });
3017
- declarations.push(...compiledStringClause.declarations);
3018
- predicateParts.push(compiledStringClause.predicate);
3019
- candidateQueryVar = compiledStringClause.candidateQueryVar;
3020
- break;
3021
- }
2607
+ if (terms.length === 0) return "cts:false-query()";
2608
+ const members = queries.map((query) => buildIncludesGroupMember(query));
2609
+ const perTermQueryExpressions = [];
2610
+ for (const term of terms) {
2611
+ const fieldQueryExpressions = [];
2612
+ for (const member of members) fieldQueryExpressions.push(member.buildTermQuery(term));
2613
+ perTermQueryExpressions.push(buildOrCtsQueryExpressionInternal(fieldQueryExpressions));
3022
2614
  }
3023
- return {
3024
- declarations,
3025
- predicate: buildPropertyPredicateExpression(predicateParts),
3026
- candidateQueryVar
3027
- };
2615
+ return buildAndCtsQueryExpressionInternal(perTermQueryExpressions);
3028
2616
  }
3029
- /**
3030
- * Build a query predicate for an XQuery string.
3031
- */
3032
- function buildQueryClause(params) {
3033
- const { query, version, queryKey, allowCtsPredicates } = params;
3034
- switch (query.target) {
3035
- case "string": return buildItemStringSearchClause({
3036
- query,
3037
- version,
3038
- queryKey,
3039
- allowCtsPredicates
3040
- });
3041
- case "title": return buildStringMatchClause({
3042
- contentNodesExpression: `identification/label/content[@xml:lang="${query.language}"]`,
3043
- value: query.value,
3044
- matchMode: query.matchMode,
3045
- isCaseSensitive: query.isCaseSensitive,
3046
- version,
3047
- queryKey,
3048
- allowCtsPredicates,
3049
- buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
3050
- containerElementName: "identification",
3051
- termExpression,
3052
- isCaseSensitive: query.isCaseSensitive,
3053
- language: query.language
3054
- })
3055
- });
3056
- case "description": return buildStringMatchClause({
3057
- contentNodesExpression: `description/content[@xml:lang="${query.language}"]`,
3058
- value: query.value,
3059
- matchMode: query.matchMode,
3060
- isCaseSensitive: query.isCaseSensitive,
3061
- version,
3062
- queryKey,
3063
- allowCtsPredicates,
3064
- buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
3065
- containerElementName: "description",
3066
- termExpression,
3067
- isCaseSensitive: query.isCaseSensitive,
3068
- language: query.language
3069
- })
3070
- });
3071
- case "periods": return buildStringMatchClause({
3072
- contentNodesExpression: `periods/period/identification/label/content[@xml:lang="${query.language}"]`,
3073
- value: query.value,
3074
- matchMode: query.matchMode,
3075
- isCaseSensitive: query.isCaseSensitive,
3076
- version,
3077
- queryKey,
3078
- allowCtsPredicates,
3079
- buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
3080
- containerElementName: "period",
3081
- termExpression,
3082
- isCaseSensitive: query.isCaseSensitive,
3083
- language: query.language
3084
- })
3085
- });
3086
- case "bibliography": return buildStringMatchClause({
3087
- contentNodesExpression: `bibliographies/bibliography/identification/label/content[@xml:lang="${query.language}"]`,
3088
- value: query.value,
3089
- matchMode: query.matchMode,
3090
- isCaseSensitive: query.isCaseSensitive,
3091
- version,
3092
- queryKey,
3093
- allowCtsPredicates,
3094
- buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
3095
- containerElementName: "bibliography",
3096
- termExpression,
3097
- isCaseSensitive: query.isCaseSensitive,
3098
- language: query.language
3099
- })
3100
- });
3101
- case "image": return buildStringMatchClause({
3102
- contentNodesExpression: `image/identification/label/content[@xml:lang="${query.language}"]`,
3103
- value: query.value,
3104
- matchMode: query.matchMode,
3105
- isCaseSensitive: query.isCaseSensitive,
3106
- version,
3107
- queryKey,
3108
- allowCtsPredicates,
3109
- buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
3110
- containerElementName: "image",
3111
- termExpression,
3112
- isCaseSensitive: query.isCaseSensitive,
3113
- language: query.language
3114
- })
3115
- });
3116
- case "notes": return buildNotesValueClause({
3117
- query,
3118
- version,
3119
- queryKey,
3120
- allowCtsPredicates
3121
- });
3122
- case "property": return buildPropertyClause({
3123
- query,
3124
- version,
3125
- queryKey,
3126
- allowCtsPredicates
3127
- });
2617
+ function buildQueryNode(query) {
2618
+ if (isQueryLeaf(query)) {
2619
+ const queryExpression = buildLeafQueryExpression(query);
2620
+ return query.isNegated === true ? buildNotCtsQueryExpression(queryExpression) : queryExpression;
3128
2621
  }
3129
- }
3130
- /**
3131
- * Build a boolean query clause for an XQuery string.
3132
- */
3133
- function buildBooleanQueryNode(params) {
3134
- const { query, version, queryKey, allowCtsPredicates } = params;
3135
- const compiledQueryClause = buildQueryClause({
3136
- query,
3137
- version,
3138
- queryKey,
3139
- allowCtsPredicates
3140
- });
3141
- const baseClause = `(${compiledQueryClause.predicate})`;
3142
- return {
3143
- declarations: compiledQueryClause.declarations,
3144
- predicate: query.isNegated ? `not(${baseClause})` : baseClause,
3145
- candidateQueryVars: query.isNegated || compiledQueryClause.candidateQueryVar == null ? [] : [compiledQueryClause.candidateQueryVar]
3146
- };
3147
- }
3148
- function buildQueryNode(params) {
3149
- const { query, version, queryKey, allowCtsPredicates } = params;
3150
- if (isQueryLeaf(query)) return buildBooleanQueryNode({
3151
- query,
3152
- version,
3153
- queryKey,
3154
- allowCtsPredicates
3155
- });
3156
- const groupQueries = getQueryGroupChildren(query);
3157
- const optimizedIncludesGroupQueries = getCompatibleIncludesGroupLeaves({
3158
- query,
3159
- version
3160
- });
3161
- if (groupQueries.length === 1) {
3162
- const onlyQuery = groupQueries[0];
3163
- if (onlyQuery == null) return {
3164
- declarations: [],
3165
- predicate: "",
3166
- candidateQueryVars: []
3167
- };
3168
- return buildQueryNode({
3169
- query: onlyQuery,
3170
- version,
3171
- queryKey,
3172
- allowCtsPredicates
3173
- });
3174
- }
3175
- if (allowCtsPredicates && optimizedIncludesGroupQueries != null) {
3176
- const compiledClause = buildIncludesGroupClause({
3177
- queries: optimizedIncludesGroupQueries,
3178
- queryKey
3179
- });
3180
- return {
3181
- declarations: compiledClause.declarations,
3182
- predicate: compiledClause.predicate,
3183
- candidateQueryVars: compiledClause.candidateQueryVar == null ? [] : [compiledClause.candidateQueryVar]
3184
- };
3185
- }
3186
- const declarations = [];
3187
- const predicates = [];
3188
- const groupOperator = getQueryGroupOperator(query);
3189
- let compiledChildNodes = groupQueries.map((groupQuery, groupIndex) => buildQueryNode({
3190
- query: groupQuery,
3191
- version,
3192
- queryKey: `${queryKey}_${groupIndex + 1}`,
3193
- allowCtsPredicates
3194
- }));
3195
- if (allowCtsPredicates && groupOperator === "or" && compiledChildNodes.some((node) => node.candidateQueryVars.length === 0)) compiledChildNodes = groupQueries.map((groupQuery, groupIndex) => buildQueryNode({
3196
- query: groupQuery,
3197
- version,
3198
- queryKey: `${queryKey}_${groupIndex + 1}`,
3199
- allowCtsPredicates: false
2622
+ const optimizedIncludesGroupQueries = getCompatibleIncludesGroupLeaves(query);
2623
+ if (optimizedIncludesGroupQueries != null) return buildIncludesGroupQueryExpression(optimizedIncludesGroupQueries);
2624
+ const childQueryExpressions = [];
2625
+ for (const childQuery of getQueryGroupChildren(query)) childQueryExpressions.push(buildQueryNode(childQuery));
2626
+ return getQueryGroupOperator(query) === "and" ? buildAndCtsQueryExpressionInternal(childQueryExpressions) : buildOrCtsQueryExpressionInternal(childQueryExpressions);
2627
+ }
2628
+ function buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, belongsToCollectionPropertyVariableUuid) {
2629
+ if (belongsToCollectionScopeUuids.length === 0) return null;
2630
+ const collectionValueQueryExpressions = [];
2631
+ for (const uuid of belongsToCollectionScopeUuids) collectionValueQueryExpressions.push(buildPlainElementAttributeValueQueryExpression({
2632
+ elementName: "value",
2633
+ attributeName: "uuid",
2634
+ value: uuid
3200
2635
  }));
3201
- const candidateQueryVarsByChild = [];
3202
- for (const compiledQueryNode of compiledChildNodes) {
3203
- declarations.push(...compiledQueryNode.declarations);
3204
- predicates.push(compiledQueryNode.predicate);
3205
- candidateQueryVarsByChild.push(compiledQueryNode.candidateQueryVars);
3206
- }
3207
- const candidateQueryVars = groupOperator === "and" ? candidateQueryVarsByChild.flat() : candidateQueryVarsByChild.every((vars) => vars.length > 0) ? candidateQueryVarsByChild.flat() : [];
3208
- return {
3209
- declarations,
3210
- predicate: groupOperator === "and" ? buildAndPredicate(predicates) : buildOrPredicate(predicates),
3211
- candidateQueryVars
3212
- };
2636
+ return buildPropertyQueryExpression({
2637
+ propertyVariable: belongsToCollectionPropertyVariableUuid,
2638
+ queryExpression: buildNestedElementQuery(["value"], buildOrCtsQueryExpressionInternal(collectionValueQueryExpressions))
2639
+ });
3213
2640
  }
3214
2641
  function buildQueryPlan(params) {
3215
- const { queries, version, baseItemsExpression } = params;
3216
- const declarations = [];
3217
- let predicate = "";
3218
- let candidateQueryVars = [];
3219
- if (queries != null) {
3220
- const compiledQueryNode = buildQueryNode({
3221
- query: queries,
3222
- version,
3223
- queryKey: "1",
3224
- allowCtsPredicates: true
3225
- });
3226
- if (compiledQueryNode.declarations.length > 0) declarations.push(`let ${CTS_INCLUDES_STOP_WORDS_VAR} := (${CTS_INCLUDES_STOP_WORDS.map((stopWord) => stringLiteral(stopWord)).join(", ")})`, ...compiledQueryNode.declarations);
3227
- predicate = compiledQueryNode.predicate;
3228
- candidateQueryVars = compiledQueryNode.candidateQueryVars;
3229
- }
3230
- let itemsExpression = `(${baseItemsExpression})`;
3231
- if (candidateQueryVars.length > 0) {
3232
- const candidateQueriesExpression = `(${candidateQueryVars.join(", ")})`;
3233
- const candidateItemsQueryVar = "$candidateItemsQuery";
3234
- declarations.push(`let ${candidateItemsQueryVar} :=
3235
- if (count(${candidateQueriesExpression}) = 1)
3236
- then ${candidateQueriesExpression}[1]
3237
- else if (count(${candidateQueriesExpression}) gt 1)
3238
- then cts:or-query(${candidateQueriesExpression})
3239
- else ()`);
3240
- itemsExpression = `(if (exists(${candidateItemsQueryVar}))
3241
- then cts:search(${baseItemsExpression}, ${candidateItemsQueryVar})
3242
- else ${baseItemsExpression})`;
3243
- }
3244
- return {
3245
- declarations,
3246
- itemsExpression,
3247
- predicate
3248
- };
2642
+ const { queries } = params;
2643
+ if (queries == null) return { queryExpression: null };
2644
+ return { queryExpression: buildQueryNode(queries) };
3249
2645
  }
3250
2646
  //#endregion
3251
2647
  //#region src/utils/fetchers/set/items.ts
@@ -3329,31 +2725,21 @@ function buildOrderedItemsClause(sort) {
3329
2725
  * For propertyValue sorting, dataType is required and the sort key uses the first valid leaf value (value[not(@i)]).
3330
2726
  * @param params.page - The page number (1-indexed)
3331
2727
  * @param params.pageSize - The number of items per page
3332
- * @param options - Options for the fetch
3333
- * @param options.version - The version of the OCHRE API to use
3334
2728
  * @returns An XQuery string
3335
2729
  */
3336
- function buildXQuery$1(params, options) {
3337
- const version = options?.version ?? 2;
2730
+ function buildXQuery$1(params) {
3338
2731
  const { queries, sort, setScopeUuids, belongsToCollectionScopeUuids, page, pageSize } = params;
3339
2732
  const startPosition = (page - 1) * pageSize + 1;
3340
2733
  const endPosition = page * pageSize;
3341
- const setScopeFilter = `/set[(${setScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")})]/items/*`;
3342
- const compiledQueryPlan = buildQueryPlan({
3343
- queries,
3344
- version,
3345
- baseItemsExpression: `${version === 2 ? "doc()" : "input()"}/ochre${setScopeFilter}`
3346
- });
3347
- const queryFilterDeclarations = compiledQueryPlan.declarations.length > 0 ? `${compiledQueryPlan.declarations.join("\n")}\n\n` : "";
3348
- const filterPredicates = [];
3349
- if (belongsToCollectionScopeUuids.length > 0) {
3350
- const belongsToCollectionScopeValues = belongsToCollectionScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ");
3351
- filterPredicates.push(`.//properties[property[label/@uuid="${BELONGS_TO_COLLECTION_UUID}" and value/(${belongsToCollectionScopeValues})]]`);
3352
- }
3353
- if (compiledQueryPlan.predicate.length > 0) filterPredicates.push(`(${compiledQueryPlan.predicate})`);
3354
- const itemFilters = filterPredicates.length > 0 ? `[${filterPredicates.join(" and ")}]` : "";
2734
+ const baseItemsExpression = `doc()/ochre${`/set[(${setScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")})]/items/*`}`;
2735
+ const compiledQueryPlan = buildQueryPlan({ queries });
2736
+ const itemsQueryExpressions = [];
2737
+ const belongsToCollectionQueryExpression = buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, BELONGS_TO_COLLECTION_UUID);
2738
+ if (compiledQueryPlan.queryExpression != null) itemsQueryExpressions.push(compiledQueryPlan.queryExpression);
2739
+ if (belongsToCollectionQueryExpression != null) itemsQueryExpressions.push(belongsToCollectionQueryExpression);
2740
+ const itemsQueryExpression = buildAndCtsQueryExpression(itemsQueryExpressions);
3355
2741
  const orderedItemsClause = buildOrderedItemsClause(sort);
3356
- return `<ochre>{${`${queryFilterDeclarations}let $items := ${compiledQueryPlan.itemsExpression}${itemFilters}
2742
+ return `<ochre>{${`${itemsQueryExpression == null ? `let $items := ${baseItemsExpression}` : `let $items := ${baseItemsExpression}[cts:contains(., ${itemsQueryExpression})]`}
3357
2743
 
3358
2744
  let $totalCount := count($items)
3359
2745
  ${orderedItemsClause}
@@ -3383,7 +2769,7 @@ function buildXQuery$1(params, options) {
3383
2769
  */
3384
2770
  async function fetchSetItems(params, itemCategories, options) {
3385
2771
  try {
3386
- const version = options?.version ?? 2;
2772
+ if (options?.version != null && options.version !== 2) throw new Error("Set item queries only support API version 2");
3387
2773
  const { setScopeUuids, belongsToCollectionScopeUuids, queries, sort, page, pageSize } = setItemsParamsSchema.parse(params);
3388
2774
  const xquery = buildXQuery$1({
3389
2775
  setScopeUuids,
@@ -3392,14 +2778,12 @@ async function fetchSetItems(params, itemCategories, options) {
3392
2778
  sort,
3393
2779
  page,
3394
2780
  pageSize
3395
- }, { version });
3396
- let response;
3397
- if (version === 2) response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&format=json", {
2781
+ });
2782
+ const response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&format=json", {
3398
2783
  method: "POST",
3399
2784
  body: xquery,
3400
2785
  headers: { "Content-Type": "application/xquery" }
3401
2786
  });
3402
- else response = await (options?.fetch ?? fetch)(`https://ochre.lib.uchicago.edu/ochre?xquery=${encodeURIComponent(xquery)}&format=json&lang="*"`);
3403
2787
  if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`);
3404
2788
  const data = await response.json();
3405
2789
  if (Array.isArray(data.result) || Object.keys(data.result).length === 0) throw new Error("Invalid OCHRE API response");
@@ -3561,6 +2945,23 @@ function getPropertyVariableUuidsFromQueries(queries) {
3561
2945
  }
3562
2946
  return [...propertyVariableUuids];
3563
2947
  }
2948
+ function getItemFilterQueriesFromPropertyValueQueries(queries) {
2949
+ if (queries == null) return null;
2950
+ if ("target" in queries) {
2951
+ if (queries.target !== "property") return queries;
2952
+ if (queries.dataType === "date" || queries.dataType === "dateTime") return queries;
2953
+ return "value" in queries && queries.value != null ? queries : null;
2954
+ }
2955
+ const filteredChildren = [];
2956
+ const childQueries = "and" in queries ? queries.and : queries.or;
2957
+ for (const childQuery of childQueries) {
2958
+ const filteredChildQuery = getItemFilterQueriesFromPropertyValueQueries(childQuery);
2959
+ if (filteredChildQuery != null) filteredChildren.push(filteredChildQuery);
2960
+ }
2961
+ if (filteredChildren.length === 0) return null;
2962
+ if (filteredChildren.length === 1) return filteredChildren[0] ?? null;
2963
+ return "and" in queries ? { and: filteredChildren } : { or: filteredChildren };
2964
+ }
3564
2965
  /**
3565
2966
  * Schema for a single property value query item in the OCHRE API response
3566
2967
  */
@@ -3634,29 +3035,20 @@ const responseSchema = z.object({ result: z.union([z.object({ ochre: z.object({
3634
3035
  * @param params.attributes.bibliographies - Whether to return values for bibliographies
3635
3036
  * @param params.attributes.periods - Whether to return values for periods
3636
3037
  * @param params.isLimitedToLeafPropertyValues - Whether to limit the property values to leaf property values
3637
- * @param options - Options for the fetch
3638
- * @param options.version - The version of the OCHRE API to use
3639
3038
  * @returns An XQuery string
3640
3039
  */
3641
- function buildXQuery(params, options) {
3642
- const version = options?.version ?? 2;
3040
+ function buildXQuery(params) {
3643
3041
  const { setScopeUuids, belongsToCollectionScopeUuids, queries, attributes, isLimitedToLeafPropertyValues } = params;
3644
3042
  let setScopeFilter = "/set/items/*";
3645
3043
  if (setScopeUuids.length > 0) setScopeFilter = `/set[(${setScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")})]/items/*`;
3646
3044
  const propertyVariableFilters = getPropertyVariableUuidsFromQueries(queries).map((uuid) => `@uuid="${uuid}"`).join(" or ");
3647
- const compiledQueryPlan = buildQueryPlan({
3648
- queries,
3649
- version,
3650
- baseItemsExpression: `${version === 2 ? "doc()" : "input()"}/ochre${setScopeFilter}`
3651
- });
3652
- const queryFilterDeclarations = compiledQueryPlan.declarations.length > 0 ? `${compiledQueryPlan.declarations.join("\n")}\n\n` : "";
3653
- const filterPredicates = [];
3654
- if (belongsToCollectionScopeUuids.length > 0) {
3655
- const belongsToCollectionScopeValues = belongsToCollectionScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ");
3656
- filterPredicates.push(`.//properties[property[label/@uuid="${BELONGS_TO_COLLECTION_UUID}" and value[${belongsToCollectionScopeValues}]]]`);
3657
- }
3658
- if (compiledQueryPlan.predicate.length > 0) filterPredicates.push(`(${compiledQueryPlan.predicate})`);
3659
- const itemFilters = filterPredicates.length > 0 ? `[${filterPredicates.join(" and ")}]` : "";
3045
+ const baseItemsExpression = `doc()/ochre${setScopeFilter}`;
3046
+ const compiledQueryPlan = buildQueryPlan({ queries: getItemFilterQueriesFromPropertyValueQueries(queries) });
3047
+ const itemsQueryExpressions = [];
3048
+ const belongsToCollectionQueryExpression = buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, BELONGS_TO_COLLECTION_UUID);
3049
+ if (compiledQueryPlan.queryExpression != null) itemsQueryExpressions.push(compiledQueryPlan.queryExpression);
3050
+ if (belongsToCollectionQueryExpression != null) itemsQueryExpressions.push(belongsToCollectionQueryExpression);
3051
+ const itemsQueryExpression = buildAndCtsQueryExpression(itemsQueryExpressions);
3660
3052
  const queryBlocks = [`let $matching-props := $items//property[label[${propertyVariableFilters}]]
3661
3053
 
3662
3054
  let $property-values :=
@@ -3686,7 +3078,7 @@ let $property-values :=
3686
3078
  return <attributeValue attributeType="periods" itemUuid="{$item/@uuid}" content="{$label}" />`);
3687
3079
  returnedSequences.push("$period-values");
3688
3080
  }
3689
- return `<ochre>{${`${queryFilterDeclarations}let $items := ${compiledQueryPlan.itemsExpression}${itemFilters}
3081
+ return `<ochre>{${`${itemsQueryExpression == null ? `let $items := ${baseItemsExpression}` : `let $items := ${baseItemsExpression}[cts:contains(., ${itemsQueryExpression})]`}
3690
3082
 
3691
3083
  ${queryBlocks.join("\n\n")}
3692
3084
 
@@ -3710,7 +3102,7 @@ return (${returnedSequences.join(", ")})`}}</ochre>`;
3710
3102
  */
3711
3103
  async function fetchSetPropertyValues(params, options) {
3712
3104
  try {
3713
- const version = options?.version ?? 2;
3105
+ if (options?.version != null && options.version !== 2) throw new Error("Set property value queries only support API version 2");
3714
3106
  const { setScopeUuids, belongsToCollectionScopeUuids, queries, attributes, isLimitedToLeafPropertyValues } = setPropertyValuesParamsSchema.parse(params);
3715
3107
  const xquery = buildXQuery({
3716
3108
  setScopeUuids,
@@ -3718,14 +3110,12 @@ async function fetchSetPropertyValues(params, options) {
3718
3110
  queries,
3719
3111
  attributes,
3720
3112
  isLimitedToLeafPropertyValues
3721
- }, { version });
3722
- let response;
3723
- if (version === 2) response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&format=json", {
3113
+ });
3114
+ const response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&format=json", {
3724
3115
  method: "POST",
3725
3116
  body: xquery,
3726
3117
  headers: { "Content-Type": "application/xquery" }
3727
3118
  });
3728
- else response = await (options?.fetch ?? fetch)(`https://ochre.lib.uchicago.edu/ochre?xquery=${encodeURIComponent(xquery)}&format=json&lang="*"`);
3729
3119
  if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`);
3730
3120
  const data = await response.json();
3731
3121
  const parsedResultRaw = responseSchema.parse(data);