ochre-sdk 0.22.1 → 0.22.3

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.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,429 @@ 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(", ")})`;
2193
+ "punctuation-insensitive",
2194
+ "whitespace-insensitive",
2195
+ "unstemmed",
2196
+ "unwildcarded"
2197
+ ].map((option) => stringLiteral(option)).join(", ")})`;
2303
2198
  }
2304
- function buildWildcardedTermExpression(termExpression) {
2305
- return `fn:concat("*", ${termExpression}, "*")`;
2306
- }
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 buildPropertyStringQueryExpression(params) {
2383
+ const { propertyVariable, value, matchMode, isCaseSensitive, language } = params;
2384
+ return buildPropertyQueryExpression({
2385
+ propertyVariable,
2386
+ queryExpression: buildNestedElementQuery(["value"], buildAndCtsQueryExpressionInternal([buildValueNotInheritedQuery(), buildValueContentInnerQuery({
2387
+ language,
2388
+ value,
2389
+ matchMode,
2390
+ isCaseSensitive
2391
+ })]))
2392
+ });
2393
+ }
2394
+ function buildPropertyScalarQueryExpression(params) {
2395
+ const { propertyVariable, value, matchMode, isCaseSensitive } = params;
2396
+ return buildPropertyQueryExpression({
2397
+ propertyVariable,
2398
+ queryExpression: buildNestedElementQuery(["value"], buildOrCtsQueryExpressionInternal([buildValueRawValueInnerQuery({
2399
+ value,
2400
+ matchMode,
2401
+ isCaseSensitive
2402
+ }), buildValueDirectTextInnerQuery({
2403
+ value,
2404
+ matchMode,
2405
+ isCaseSensitive
2406
+ })]))
2407
+ });
2408
+ }
2409
+ function buildPropertyAllQueryExpression(params) {
2410
+ const { query, value, matchMode } = params;
2411
+ if (matchMode === "exact") return buildPropertyQueryExpression({
2412
+ propertyVariable: query.propertyVariable,
2413
+ queryExpression: buildNestedElementQuery(["value"], buildAndCtsQueryExpressionInternal([buildValueNotIdRefQuery(), buildCtsWordQueryExpression({
2414
+ value,
2415
+ matchMode,
2416
+ isCaseSensitive: query.isCaseSensitive
2417
+ })]))
2418
+ });
2419
+ return buildPropertyQueryExpression({
2420
+ propertyVariable: query.propertyVariable,
2421
+ queryExpression: buildNestedElementQuery(["value"], buildAndCtsQueryExpressionInternal([buildValueNotIdRefQuery(), buildOrCtsQueryExpressionInternal([
2422
+ buildValueRawValueInnerQuery({
2423
+ value,
2424
+ matchMode,
2425
+ isCaseSensitive: query.isCaseSensitive
2426
+ }),
2427
+ buildValueDirectTextInnerQuery({
2428
+ value,
2429
+ matchMode,
2430
+ isCaseSensitive: query.isCaseSensitive
2431
+ }),
2432
+ buildValueContentInnerQuery({
2433
+ language: query.language,
2434
+ value,
2435
+ matchMode,
2436
+ isCaseSensitive: query.isCaseSensitive
2437
+ })
2438
+ ])]))
2439
+ });
2440
+ }
2441
+ function buildPropertyIdRefQueryExpression(params) {
2442
+ const { propertyVariable, value } = params;
2443
+ return buildPropertyQueryExpression({
2444
+ propertyVariable,
2445
+ queryExpression: buildNestedElementQuery(["value"], buildPlainElementAttributeValueQueryExpression({
2446
+ elementName: "value",
2447
+ attributeName: "uuid",
2448
+ value
2449
+ }))
2450
+ });
2451
+ }
2452
+ function buildPropertyDateRangeQueryExpression(query) {
2453
+ const rangeQueryExpressions = [];
2454
+ if (query.from != null) rangeQueryExpressions.push(`cts:element-attribute-range-query(xs:QName("value"), xs:QName("rawValue"), ">=", xs:${query.dataType}(${stringLiteral(query.from)}))`);
2455
+ if (query.to != null) rangeQueryExpressions.push(`cts:element-attribute-range-query(xs:QName("value"), xs:QName("rawValue"), "<=", xs:${query.dataType}(${stringLiteral(query.to)}))`);
2456
+ return buildPropertyQueryExpression({
2457
+ propertyVariable: query.propertyVariable,
2458
+ queryExpression: buildNestedElementQuery(["value"], buildAndCtsQueryExpressionInternal(rangeQueryExpressions))
2459
+ });
2460
+ }
2461
+ function buildItemStringQueryExpression(params) {
2462
+ const { value, matchMode, isCaseSensitive, language } = params;
2463
+ return buildOrCtsQueryExpressionInternal([buildContentTargetQueryExpression({
2464
+ target: "title",
2465
+ value,
2466
+ matchMode,
2467
+ isCaseSensitive,
2468
+ language
2469
+ }), buildPropertyStringQueryExpression({
2470
+ value,
2471
+ matchMode,
2472
+ isCaseSensitive,
2473
+ language
2474
+ })]);
2475
+ }
2476
+ function getLeafSearchValue(query) {
2623
2477
  switch (query.target) {
2624
2478
  case "string":
2625
2479
  case "title":
2626
2480
  case "description":
2627
2481
  case "image":
2628
- case "notes":
2629
2482
  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;
2483
+ case "bibliography":
2484
+ case "notes": return query.value;
2485
+ case "property": return "value" in query && query.value != null ? query.value : null;
2634
2486
  }
2635
2487
  }
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),
2488
+ function buildLeafValueQueryExpression(params) {
2489
+ const { query, value, matchMode } = params;
2490
+ switch (query.target) {
2491
+ case "string": return buildItemStringQueryExpression({
2647
2492
  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,
2493
+ matchMode,
2670
2494
  isCaseSensitive: query.isCaseSensitive,
2671
2495
  language: query.language
2672
- })},
2673
- ${buildItemStringPropertyValueBranch({
2674
- termExpression,
2496
+ });
2497
+ case "notes": return buildNotesQueryExpression({
2498
+ value,
2499
+ matchMode,
2675
2500
  isCaseSensitive: query.isCaseSensitive,
2676
2501
  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,
2502
+ });
2503
+ case "title":
2504
+ case "description":
2505
+ case "image":
2506
+ case "periods":
2507
+ case "bibliography": return buildContentTargetQueryExpression({
2508
+ target: query.target,
2509
+ value,
2510
+ matchMode,
2698
2511
  isCaseSensitive: query.isCaseSensitive,
2699
2512
  language: query.language
2700
- })
2701
- };
2513
+ });
2514
+ case "property": switch (query.dataType) {
2515
+ case "all": return buildPropertyAllQueryExpression({
2516
+ query,
2517
+ value,
2518
+ matchMode
2519
+ });
2520
+ case "IDREF": return buildPropertyIdRefQueryExpression({
2521
+ propertyVariable: query.propertyVariable,
2522
+ value
2523
+ });
2524
+ case "string": return buildPropertyStringQueryExpression({
2525
+ propertyVariable: query.propertyVariable,
2526
+ value,
2527
+ matchMode,
2528
+ isCaseSensitive: query.isCaseSensitive,
2529
+ language: query.language
2530
+ });
2531
+ case "integer":
2532
+ case "decimal":
2533
+ case "time":
2534
+ case "boolean":
2535
+ case "date":
2536
+ case "dateTime": return buildPropertyScalarQueryExpression({
2537
+ propertyVariable: query.propertyVariable,
2538
+ value,
2539
+ matchMode,
2540
+ isCaseSensitive: query.isCaseSensitive
2541
+ });
2542
+ }
2543
+ }
2702
2544
  }
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
- };
2545
+ function buildLeafQueryExpression(query) {
2546
+ if (query.target === "property" && (query.dataType === "date" || query.dataType === "dateTime") && query.value == null) return buildPropertyDateRangeQueryExpression(query);
2547
+ const searchValue = getLeafSearchValue(query);
2548
+ if (searchValue == null) throw new Error("Missing searchable value for query leaf");
2549
+ if (query.matchMode === "exact") return buildLeafValueQueryExpression({
2550
+ query,
2551
+ value: searchValue,
2552
+ matchMode: "exact"
2553
+ });
2554
+ const terms = tokenizeIncludesSearchValue({
2555
+ value: searchValue,
2556
+ isCaseSensitive: query.isCaseSensitive
2557
+ });
2558
+ if (terms.length === 0) return "cts:false-query()";
2559
+ const termQueryExpressions = [];
2560
+ for (const term of terms) termQueryExpressions.push(buildLeafValueQueryExpression({
2561
+ query,
2562
+ value: term,
2563
+ matchMode: "includes"
2564
+ }));
2565
+ return buildAndCtsQueryExpressionInternal(termQueryExpressions);
2717
2566
  }
2718
- function buildIncludesGroupMember(query) {
2567
+ function getGroupableIncludesValue(query) {
2568
+ if (query.matchMode !== "includes" || query.isNegated === true) return null;
2719
2569
  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);
2570
+ case "string":
2571
+ case "title":
2572
+ case "description":
2573
+ case "image":
2574
+ case "periods":
2575
+ case "bibliography":
2576
+ case "notes": return query.value;
2757
2577
  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);
2578
+ if (!("value" in query) || query.value == null || query.dataType === "IDREF") return null;
2579
+ return query.value;
2760
2580
  }
2761
2581
  }
2762
- function getCompatibleIncludesGroupLeaves(params) {
2763
- const { query, version } = params;
2582
+ function isQueryLeaf(query) {
2583
+ return "target" in query;
2584
+ }
2585
+ function getQueryGroupChildren(query) {
2586
+ return "and" in query ? query.and : query.or;
2587
+ }
2588
+ function getQueryGroupOperator(query) {
2589
+ return "and" in query ? "and" : "or";
2590
+ }
2591
+ function getCompatibleIncludesGroupLeaves(query) {
2764
2592
  if (!("or" in query) || query.or.length <= 1) return null;
2765
2593
  const leafQueries = [];
2766
2594
  for (const childQuery of query.or) {
@@ -2770,482 +2598,64 @@ function getCompatibleIncludesGroupLeaves(params) {
2770
2598
  const firstQuery = leafQueries[0];
2771
2599
  if (firstQuery == null) return null;
2772
2600
  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;
2601
+ if (groupValue == null) return null;
2602
+ for (const leafQuery of leafQueries) if (getGroupableIncludesValue(leafQuery) !== groupValue || leafQuery.isCaseSensitive !== firstQuery.isCaseSensitive || leafQuery.language !== firstQuery.language) return null;
2787
2603
  return leafQueries;
2788
2604
  }
2789
- function buildIncludesGroupClause(params) {
2790
- const { queries, queryKey } = params;
2605
+ function buildIncludesGroupMember(query) {
2606
+ return { buildTermQuery: (term) => buildLeafValueQueryExpression({
2607
+ query,
2608
+ value: term,
2609
+ matchMode: "includes"
2610
+ }) };
2611
+ }
2612
+ function buildIncludesGroupQueryExpression(queries) {
2791
2613
  const firstQuery = queries[0];
2792
2614
  if (firstQuery == null) throw new Error("Cannot build an includes group without queries");
2793
2615
  const groupValue = getGroupableIncludesValue(firstQuery);
2794
2616
  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({
2617
+ const terms = tokenizeIncludesSearchValue({
2802
2618
  value: groupValue,
2803
2619
  isCaseSensitive: firstQuery.isCaseSensitive
2804
2620
  });
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
- }
2621
+ if (terms.length === 0) return "cts:false-query()";
2622
+ const members = queries.map((query) => buildIncludesGroupMember(query));
2623
+ const perTermQueryExpressions = [];
2624
+ for (const term of terms) {
2625
+ const fieldQueryExpressions = [];
2626
+ for (const member of members) fieldQueryExpressions.push(member.buildTermQuery(term));
2627
+ perTermQueryExpressions.push(buildOrCtsQueryExpressionInternal(fieldQueryExpressions));
3022
2628
  }
3023
- return {
3024
- declarations,
3025
- predicate: buildPropertyPredicateExpression(predicateParts),
3026
- candidateQueryVar
3027
- };
2629
+ return buildAndCtsQueryExpressionInternal(perTermQueryExpressions);
3028
2630
  }
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
- });
2631
+ function buildQueryNode(query) {
2632
+ if (isQueryLeaf(query)) {
2633
+ const queryExpression = buildLeafQueryExpression(query);
2634
+ return query.isNegated === true ? buildNotCtsQueryExpression(queryExpression) : queryExpression;
3128
2635
  }
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
2636
+ const optimizedIncludesGroupQueries = getCompatibleIncludesGroupLeaves(query);
2637
+ if (optimizedIncludesGroupQueries != null) return buildIncludesGroupQueryExpression(optimizedIncludesGroupQueries);
2638
+ const childQueryExpressions = [];
2639
+ for (const childQuery of getQueryGroupChildren(query)) childQueryExpressions.push(buildQueryNode(childQuery));
2640
+ return getQueryGroupOperator(query) === "and" ? buildAndCtsQueryExpressionInternal(childQueryExpressions) : buildOrCtsQueryExpressionInternal(childQueryExpressions);
2641
+ }
2642
+ function buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, belongsToCollectionPropertyVariableUuid) {
2643
+ if (belongsToCollectionScopeUuids.length === 0) return null;
2644
+ const collectionValueQueryExpressions = [];
2645
+ for (const uuid of belongsToCollectionScopeUuids) collectionValueQueryExpressions.push(buildPlainElementAttributeValueQueryExpression({
2646
+ elementName: "value",
2647
+ attributeName: "uuid",
2648
+ value: uuid
3200
2649
  }));
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
- };
2650
+ return buildPropertyQueryExpression({
2651
+ propertyVariable: belongsToCollectionPropertyVariableUuid,
2652
+ queryExpression: buildNestedElementQuery(["value"], buildOrCtsQueryExpressionInternal(collectionValueQueryExpressions))
2653
+ });
3213
2654
  }
3214
2655
  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
- };
2656
+ const { queries } = params;
2657
+ if (queries == null) return { queryExpression: null };
2658
+ return { queryExpression: buildQueryNode(queries) };
3249
2659
  }
3250
2660
  //#endregion
3251
2661
  //#region src/utils/fetchers/set/items.ts
@@ -3329,31 +2739,21 @@ function buildOrderedItemsClause(sort) {
3329
2739
  * For propertyValue sorting, dataType is required and the sort key uses the first valid leaf value (value[not(@i)]).
3330
2740
  * @param params.page - The page number (1-indexed)
3331
2741
  * @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
2742
  * @returns An XQuery string
3335
2743
  */
3336
- function buildXQuery$1(params, options) {
3337
- const version = options?.version ?? 2;
2744
+ function buildXQuery$1(params) {
3338
2745
  const { queries, sort, setScopeUuids, belongsToCollectionScopeUuids, page, pageSize } = params;
3339
2746
  const startPosition = (page - 1) * pageSize + 1;
3340
2747
  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 ")}]` : "";
2748
+ const baseItemsExpression = `doc()/ochre${`/set[(${setScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")})]/items/*`}`;
2749
+ const compiledQueryPlan = buildQueryPlan({ queries });
2750
+ const itemsQueryExpressions = [];
2751
+ const belongsToCollectionQueryExpression = buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, BELONGS_TO_COLLECTION_UUID);
2752
+ if (compiledQueryPlan.queryExpression != null) itemsQueryExpressions.push(compiledQueryPlan.queryExpression);
2753
+ if (belongsToCollectionQueryExpression != null) itemsQueryExpressions.push(belongsToCollectionQueryExpression);
2754
+ const itemsQueryExpression = buildAndCtsQueryExpression(itemsQueryExpressions);
3355
2755
  const orderedItemsClause = buildOrderedItemsClause(sort);
3356
- return `<ochre>{${`${queryFilterDeclarations}let $items := ${compiledQueryPlan.itemsExpression}${itemFilters}
2756
+ return `<ochre>{${`${itemsQueryExpression == null ? `let $items := ${baseItemsExpression}` : `let $items := ${baseItemsExpression}[cts:contains(., ${itemsQueryExpression})]`}
3357
2757
 
3358
2758
  let $totalCount := count($items)
3359
2759
  ${orderedItemsClause}
@@ -3383,7 +2783,7 @@ function buildXQuery$1(params, options) {
3383
2783
  */
3384
2784
  async function fetchSetItems(params, itemCategories, options) {
3385
2785
  try {
3386
- const version = options?.version ?? 2;
2786
+ if (options?.version != null && options.version !== 2) throw new Error("Set item queries only support API version 2");
3387
2787
  const { setScopeUuids, belongsToCollectionScopeUuids, queries, sort, page, pageSize } = setItemsParamsSchema.parse(params);
3388
2788
  const xquery = buildXQuery$1({
3389
2789
  setScopeUuids,
@@ -3392,14 +2792,12 @@ async function fetchSetItems(params, itemCategories, options) {
3392
2792
  sort,
3393
2793
  page,
3394
2794
  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", {
2795
+ });
2796
+ const response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&format=json", {
3398
2797
  method: "POST",
3399
2798
  body: xquery,
3400
2799
  headers: { "Content-Type": "application/xquery" }
3401
2800
  });
3402
- else response = await (options?.fetch ?? fetch)(`https://ochre.lib.uchicago.edu/ochre?xquery=${encodeURIComponent(xquery)}&format=json&lang="*"`);
3403
2801
  if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`);
3404
2802
  const data = await response.json();
3405
2803
  if (Array.isArray(data.result) || Object.keys(data.result).length === 0) throw new Error("Invalid OCHRE API response");
@@ -3634,29 +3032,20 @@ const responseSchema = z.object({ result: z.union([z.object({ ochre: z.object({
3634
3032
  * @param params.attributes.bibliographies - Whether to return values for bibliographies
3635
3033
  * @param params.attributes.periods - Whether to return values for periods
3636
3034
  * @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
3035
  * @returns An XQuery string
3640
3036
  */
3641
- function buildXQuery(params, options) {
3642
- const version = options?.version ?? 2;
3037
+ function buildXQuery(params) {
3643
3038
  const { setScopeUuids, belongsToCollectionScopeUuids, queries, attributes, isLimitedToLeafPropertyValues } = params;
3644
3039
  let setScopeFilter = "/set/items/*";
3645
3040
  if (setScopeUuids.length > 0) setScopeFilter = `/set[(${setScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")})]/items/*`;
3646
3041
  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 ")}]` : "";
3042
+ const baseItemsExpression = `doc()/ochre${setScopeFilter}`;
3043
+ const compiledQueryPlan = buildQueryPlan({ queries });
3044
+ const itemsQueryExpressions = [];
3045
+ const belongsToCollectionQueryExpression = buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, BELONGS_TO_COLLECTION_UUID);
3046
+ if (compiledQueryPlan.queryExpression != null) itemsQueryExpressions.push(compiledQueryPlan.queryExpression);
3047
+ if (belongsToCollectionQueryExpression != null) itemsQueryExpressions.push(belongsToCollectionQueryExpression);
3048
+ const itemsQueryExpression = buildAndCtsQueryExpression(itemsQueryExpressions);
3660
3049
  const queryBlocks = [`let $matching-props := $items//property[label[${propertyVariableFilters}]]
3661
3050
 
3662
3051
  let $property-values :=
@@ -3686,7 +3075,7 @@ let $property-values :=
3686
3075
  return <attributeValue attributeType="periods" itemUuid="{$item/@uuid}" content="{$label}" />`);
3687
3076
  returnedSequences.push("$period-values");
3688
3077
  }
3689
- return `<ochre>{${`${queryFilterDeclarations}let $items := ${compiledQueryPlan.itemsExpression}${itemFilters}
3078
+ return `<ochre>{${`${itemsQueryExpression == null ? `let $items := ${baseItemsExpression}` : `let $items := ${baseItemsExpression}[cts:contains(., ${itemsQueryExpression})]`}
3690
3079
 
3691
3080
  ${queryBlocks.join("\n\n")}
3692
3081
 
@@ -3710,7 +3099,7 @@ return (${returnedSequences.join(", ")})`}}</ochre>`;
3710
3099
  */
3711
3100
  async function fetchSetPropertyValues(params, options) {
3712
3101
  try {
3713
- const version = options?.version ?? 2;
3102
+ if (options?.version != null && options.version !== 2) throw new Error("Set property value queries only support API version 2");
3714
3103
  const { setScopeUuids, belongsToCollectionScopeUuids, queries, attributes, isLimitedToLeafPropertyValues } = setPropertyValuesParamsSchema.parse(params);
3715
3104
  const xquery = buildXQuery({
3716
3105
  setScopeUuids,
@@ -3718,14 +3107,12 @@ async function fetchSetPropertyValues(params, options) {
3718
3107
  queries,
3719
3108
  attributes,
3720
3109
  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", {
3110
+ });
3111
+ const response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&format=json", {
3724
3112
  method: "POST",
3725
3113
  body: xquery,
3726
3114
  headers: { "Content-Type": "application/xquery" }
3727
3115
  });
3728
- else response = await (options?.fetch ?? fetch)(`https://ochre.lib.uchicago.edu/ochre?xquery=${encodeURIComponent(xquery)}&format=json&lang="*"`);
3729
3116
  if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`);
3730
3117
  const data = await response.json();
3731
3118
  const parsedResultRaw = responseSchema.parse(data);
@@ -4703,6 +4090,8 @@ function parseWebElementProperties(componentProperty, elementResource) {
4703
4090
  variant ??= "detailed";
4704
4091
  let paginationVariant = getPropertyValueContentByLabel(componentProperty.properties, "pagination-variant");
4705
4092
  paginationVariant ??= "default";
4093
+ let loadingVariant = getPropertyValueContentByLabel(componentProperty.properties, "loading-variant");
4094
+ loadingVariant ??= "spinner";
4706
4095
  let layout = getPropertyValueContentByLabel(componentProperty.properties, "layout");
4707
4096
  layout ??= "image-start";
4708
4097
  properties = {
@@ -4710,13 +4099,16 @@ function parseWebElementProperties(componentProperty, elementResource) {
4710
4099
  linkUuids: setLinks.map((link) => link.uuid).filter((uuid) => uuid !== null),
4711
4100
  items,
4712
4101
  options,
4713
- displayedProperties: displayedProperties?.values.filter((value) => value.uuid !== null).map((value) => ({
4714
- uuid: value.uuid,
4715
- label: value.content?.toString() ?? ""
4716
- })) ?? null,
4717
- variant,
4718
- paginationVariant,
4719
- layout
4102
+ collectionProperties: {
4103
+ displayedProperties: displayedProperties?.values.filter((value) => value.uuid !== null).map((value) => ({
4104
+ uuid: value.uuid,
4105
+ label: value.content?.toString() ?? ""
4106
+ })) ?? null,
4107
+ variant,
4108
+ paginationVariant,
4109
+ loadingVariant,
4110
+ layout
4111
+ }
4720
4112
  };
4721
4113
  break;
4722
4114
  }