ochre-sdk 1.3.2 → 1.3.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.
- package/dist/fetchers/container-items.mjs +2 -1
- package/dist/fetchers/gallery.mjs +9 -5
- package/dist/fetchers/item-children.mjs +4 -3
- package/dist/fetchers/item-links.mjs +8 -5
- package/dist/fetchers/item.mjs +10 -5
- package/dist/fetchers/set/item-locations.mjs +2 -1
- package/dist/fetchers/set/property-values.mjs +2 -2
- package/dist/fetchers/website-metadata.mjs +1 -1
- package/dist/getters.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/parsers/helpers.d.mts +11 -0
- package/dist/parsers/helpers.mjs +9 -5
- package/dist/parsers/index.mjs +1 -1
- package/dist/parsers/multilingual.d.mts +6 -10
- package/dist/parsers/multilingual.mjs +2 -8
- package/dist/parsers/website/components.mjs +22 -1
- package/dist/parsers/website/options.mjs +1 -1
- package/dist/parsers/website/properties.mjs +3 -1
- package/dist/parsers/website/reader.d.mts +1 -0
- package/dist/parsers/website/reader.mjs +8 -0
- package/dist/query.d.mts +3 -1
- package/dist/query.mjs +137 -76
- package/dist/schemas.mjs +1 -0
- package/dist/types/index.d.mts +11 -0
- package/dist/types/website.d.mts +12 -0
- package/dist/xquery.d.mts +21 -0
- package/dist/xquery.mjs +72 -8
- package/package.json +5 -5
|
@@ -2,7 +2,7 @@ import { requestOchre } from "./request.mjs";
|
|
|
2
2
|
import { parseRequestedLanguages, resolveContentLanguages } from "../parsers/languages.mjs";
|
|
3
3
|
import { parseSetItems } from "../parsers/index.mjs";
|
|
4
4
|
import { XMLSetItemsData } from "../xml/schemas.mjs";
|
|
5
|
-
import { stringLiteral } from "../xquery.mjs";
|
|
5
|
+
import { getContentLanguage, stringLiteral } from "../xquery.mjs";
|
|
6
6
|
import { compileContainerItemsQuery } from "../query.mjs";
|
|
7
7
|
//#region src/fetchers/container-items.ts
|
|
8
8
|
function hasArray(items) {
|
|
@@ -149,6 +149,7 @@ async function fetchContainerItems(parameters) {
|
|
|
149
149
|
scopeUuids,
|
|
150
150
|
belongsToCollectionScopeUuids,
|
|
151
151
|
queries,
|
|
152
|
+
contentLanguage: getContentLanguage(options),
|
|
152
153
|
body: ({ items, omitSupplemental }) => ` let $totalCount := count(${items})
|
|
153
154
|
${buildOrderedItemsClause(sort)}
|
|
154
155
|
let $pagedItems := subsequence($orderedItems, ${startPosition}, ${pageSize})
|
|
@@ -4,14 +4,16 @@ import { gallerySchema } from "../schemas.mjs";
|
|
|
4
4
|
import { parseRequestedLanguages, resolveContentLanguages } from "../parsers/languages.mjs";
|
|
5
5
|
import { parseGallery } from "../parsers/index.mjs";
|
|
6
6
|
import { XMLGalleryData } from "../xml/schemas.mjs";
|
|
7
|
-
import { OCHRE_RESPONSE_PADDING, compileOchreQuery, stringLiteral } from "../xquery.mjs";
|
|
7
|
+
import { OCHRE_RESPONSE_PADDING, compileOchreQuery, getContentLanguage, stringLiteral } from "../xquery.mjs";
|
|
8
8
|
import * as v from "valibot";
|
|
9
9
|
//#region src/fetchers/gallery.ts
|
|
10
10
|
function buildXQuery(parameters) {
|
|
11
|
-
const { uuid, filter, page, perPage } = parameters;
|
|
11
|
+
const { uuid, filter, page, perPage, contentLanguage } = parameters;
|
|
12
12
|
const start = (page - 1) * perPage + 1;
|
|
13
13
|
const filterLiteral = stringLiteral(filter?.trim() ?? "");
|
|
14
|
-
return compileOchreQuery({
|
|
14
|
+
return compileOchreQuery({
|
|
15
|
+
contentLanguage,
|
|
16
|
+
body: ({ omitSupplemental }) => `<ochre>${OCHRE_RESPONSE_PADDING}{
|
|
15
17
|
for $q in doc()/ochre[@uuid=${stringLiteral(uuid)}]
|
|
16
18
|
let $filter := ${filterLiteral}
|
|
17
19
|
let $resources := $q//items/resource
|
|
@@ -27,7 +29,8 @@ function buildXQuery(parameters) {
|
|
|
27
29
|
subsequence($filtered, ${start}, ${perPage})
|
|
28
30
|
)`)}
|
|
29
31
|
}</gallery>
|
|
30
|
-
}</ochre>`
|
|
32
|
+
}</ochre>`
|
|
33
|
+
});
|
|
31
34
|
}
|
|
32
35
|
async function fetchGallery(parameters, options) {
|
|
33
36
|
try {
|
|
@@ -38,7 +41,8 @@ async function fetchGallery(parameters, options) {
|
|
|
38
41
|
uuid,
|
|
39
42
|
filter,
|
|
40
43
|
page,
|
|
41
|
-
perPage
|
|
44
|
+
perPage,
|
|
45
|
+
contentLanguage: getContentLanguage(options)
|
|
42
46
|
}),
|
|
43
47
|
schema: XMLGalleryData,
|
|
44
48
|
label: "OCHRE gallery",
|
|
@@ -5,13 +5,14 @@ import { uuidSchema } from "../schemas.mjs";
|
|
|
5
5
|
import { parseRequestedLanguages, resolveContentLanguages } from "../parsers/languages.mjs";
|
|
6
6
|
import { parseLinkedItems } from "../parsers/index.mjs";
|
|
7
7
|
import { XMLItemLinksData } from "../xml/schemas.mjs";
|
|
8
|
-
import { OCHRE_RESPONSE_PADDING, compileOchreQuery, stringLiteral } from "../xquery.mjs";
|
|
8
|
+
import { OCHRE_RESPONSE_PADDING, compileOchreQuery, getContentLanguage, stringLiteral } from "../xquery.mjs";
|
|
9
9
|
import * as v from "valibot";
|
|
10
10
|
//#region src/fetchers/item-children.ts
|
|
11
|
-
function buildXQuery(uuid, category) {
|
|
11
|
+
function buildXQuery(uuid, category, contentLanguage) {
|
|
12
12
|
const categories = category == null ? OCHRE_COLLECTION_CATEGORIES : typeof category === "string" ? [category] : category;
|
|
13
13
|
const collectionQueries = Array.from(categories, (possibleCategory) => `cts:search(fn:collection("ochre/${possibleCategory}")/ochre, $uuid-query)`);
|
|
14
14
|
return compileOchreQuery({
|
|
15
|
+
contentLanguage,
|
|
15
16
|
declarations: [`declare function local:item-children($nodes as node()*) as node()* {
|
|
16
17
|
for $node in $nodes
|
|
17
18
|
return
|
|
@@ -59,7 +60,7 @@ async function fetchItemChildren(uuid, options) {
|
|
|
59
60
|
const parsedUuid = v.parse(uuidSchema, uuid);
|
|
60
61
|
const requestedLanguages = parseRequestedLanguages(options?.languages);
|
|
61
62
|
const output = await requestOchre({
|
|
62
|
-
xquery: buildXQuery(parsedUuid, options?.category),
|
|
63
|
+
xquery: buildXQuery(parsedUuid, options?.category, getContentLanguage(options)),
|
|
63
64
|
schema: XMLItemLinksData,
|
|
64
65
|
label: "OCHRE item children",
|
|
65
66
|
options
|
|
@@ -5,7 +5,7 @@ import { uuidSchema } from "../schemas.mjs";
|
|
|
5
5
|
import { parseRequestedLanguages, resolveContentLanguages } from "../parsers/languages.mjs";
|
|
6
6
|
import { parseLinkedItems } from "../parsers/index.mjs";
|
|
7
7
|
import { XMLItemLinksData } from "../xml/schemas.mjs";
|
|
8
|
-
import { OCHRE_RESPONSE_PADDING, compileOchreQuery, stringLiteral } from "../xquery.mjs";
|
|
8
|
+
import { OCHRE_RESPONSE_PADDING, compileOchreQuery, getContentLanguage, stringLiteral } from "../xquery.mjs";
|
|
9
9
|
import * as v from "valibot";
|
|
10
10
|
//#region src/fetchers/item-links.ts
|
|
11
11
|
/**
|
|
@@ -14,7 +14,7 @@ import * as v from "valibot";
|
|
|
14
14
|
* @param uuid - The UUID of the OCHRE item whose links should be fetched
|
|
15
15
|
* @returns An XQuery string
|
|
16
16
|
*/
|
|
17
|
-
function buildXQuery(uuid) {
|
|
17
|
+
function buildXQuery(uuid, contentLanguage) {
|
|
18
18
|
const linkedItems = `for $link at $position in $link-nodes
|
|
19
19
|
let $uuid := $link/@uuid/string()
|
|
20
20
|
let $category := name($link)
|
|
@@ -24,7 +24,9 @@ function buildXQuery(uuid) {
|
|
|
24
24
|
return `if (${ITEM_CATEGORY_ALIASES[category].map((alias) => `$category = ${stringLiteral(alias)}`).join(" or ")}) then fn:collection("ochre/${category}")/ochre/${category}[@uuid = $uuid]`;
|
|
25
25
|
}).join("\n else ")}
|
|
26
26
|
else ()`;
|
|
27
|
-
return compileOchreQuery({
|
|
27
|
+
return compileOchreQuery({
|
|
28
|
+
contentLanguage,
|
|
29
|
+
body: ({ omitSupplemental }) => `<ochre>${OCHRE_RESPONSE_PADDING}{let $item-uuid := ${stringLiteral(uuid)}
|
|
28
30
|
|
|
29
31
|
let $source-items := (
|
|
30
32
|
${OCHRE_COLLECTION_CATEGORIES.map((category) => ` fn:collection("ochre/${category}")/ochre[@uuid = $item-uuid]/${category}`).join(",\n")}
|
|
@@ -39,14 +41,15 @@ let $link-nodes := (
|
|
|
39
41
|
return
|
|
40
42
|
<items>{
|
|
41
43
|
${omitSupplemental(linkedItems)}
|
|
42
|
-
}</items>}</ochre>`
|
|
44
|
+
}</items>}</ochre>`
|
|
45
|
+
});
|
|
43
46
|
}
|
|
44
47
|
async function fetchItemLinks(uuid, options) {
|
|
45
48
|
try {
|
|
46
49
|
const parsedUuid = v.parse(uuidSchema, uuid);
|
|
47
50
|
const requestedLanguages = parseRequestedLanguages(options?.languages);
|
|
48
51
|
const output = await requestOchre({
|
|
49
|
-
xquery: buildXQuery(parsedUuid),
|
|
52
|
+
xquery: buildXQuery(parsedUuid, getContentLanguage(options)),
|
|
50
53
|
schema: XMLItemLinksData,
|
|
51
54
|
label: "OCHRE item links",
|
|
52
55
|
options
|
package/dist/fetchers/item.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { uuidSchema } from "../schemas.mjs";
|
|
|
5
5
|
import { parseRequestedLanguages } from "../parsers/languages.mjs";
|
|
6
6
|
import { parseItem } from "../parsers/index.mjs";
|
|
7
7
|
import { XMLData } from "../xml/schemas.mjs";
|
|
8
|
-
import { OCHRE_RESPONSE_PADDING, compileOchreQuery, stringLiteral } from "../xquery.mjs";
|
|
8
|
+
import { OCHRE_RESPONSE_PADDING, compileOchreQuery, getContentLanguage, stringLiteral } from "../xquery.mjs";
|
|
9
9
|
import { parseWebpageView } from "../parsers/website/index.mjs";
|
|
10
10
|
import * as v from "valibot";
|
|
11
11
|
//#region src/fetchers/item.ts
|
|
@@ -28,10 +28,11 @@ function assertItemCategoryAllowed(category, containedItemCategory) {
|
|
|
28
28
|
* @param parameters - The parameters for the fetch
|
|
29
29
|
* @param parameters.uuid - The UUID of the OCHRE item to fetch
|
|
30
30
|
* @param parameters.shouldOmitEmbeddedItems - Whether to drop the embedded item hierarchy
|
|
31
|
+
* @param parameters.contentLanguage - Keep only this language of every multilingual field
|
|
31
32
|
* @returns An XQuery string
|
|
32
33
|
*/
|
|
33
34
|
function buildXQuery(parameters) {
|
|
34
|
-
const { uuid, shouldOmitEmbeddedItems } = parameters;
|
|
35
|
+
const { uuid, shouldOmitEmbeddedItems, contentLanguage } = parameters;
|
|
35
36
|
const letClauses = [`let $ochre := doc(${stringLiteral(uuid)})/ochre`];
|
|
36
37
|
let itemNodesExpression = "$ochre/node()";
|
|
37
38
|
if (shouldOmitEmbeddedItems) {
|
|
@@ -46,13 +47,16 @@ ${ITEM_CATEGORIES_WITH_EMBEDDED_ITEMS.map((category) => ` $ochre/${category}`).
|
|
|
46
47
|
else $node
|
|
47
48
|
)`;
|
|
48
49
|
}
|
|
49
|
-
return compileOchreQuery({
|
|
50
|
+
return compileOchreQuery({
|
|
51
|
+
contentLanguage,
|
|
52
|
+
body: ({ omitSupplemental }) => `${letClauses.join("\n")}
|
|
50
53
|
return
|
|
51
54
|
if (empty($ochre)) then <ochre>${OCHRE_RESPONSE_PADDING}</ochre>
|
|
52
55
|
else element ochre {
|
|
53
56
|
$ochre/@*,
|
|
54
57
|
${omitSupplemental(itemNodesExpression)}
|
|
55
|
-
}`
|
|
58
|
+
}`
|
|
59
|
+
});
|
|
56
60
|
}
|
|
57
61
|
function omitEmbeddedItems(item) {
|
|
58
62
|
const { items: _items, ...itemWithoutEmbeddedItems } = item;
|
|
@@ -67,7 +71,8 @@ async function fetchItem(uuid, options) {
|
|
|
67
71
|
const output = await requestOchre({
|
|
68
72
|
xquery: buildXQuery({
|
|
69
73
|
uuid: parsedUuid,
|
|
70
|
-
shouldOmitEmbeddedItems
|
|
74
|
+
shouldOmitEmbeddedItems,
|
|
75
|
+
contentLanguage: getContentLanguage(options)
|
|
71
76
|
}),
|
|
72
77
|
schema: XMLData,
|
|
73
78
|
label: "OCHRE item",
|
|
@@ -6,7 +6,7 @@ import { parseRequiredContentLike } from "../../parsers/helpers.mjs";
|
|
|
6
6
|
import { parseRequestedLanguages, resolveContentLanguages } from "../../parsers/languages.mjs";
|
|
7
7
|
import { parseCoordinates, parseIdentification } from "../../parsers/index.mjs";
|
|
8
8
|
import { XMLSetItemLocationsData } from "../../xml/schemas.mjs";
|
|
9
|
-
import { stringLiteral } from "../../xquery.mjs";
|
|
9
|
+
import { getContentLanguage, stringLiteral } from "../../xquery.mjs";
|
|
10
10
|
import { compileContainerItemsQuery } from "../../query.mjs";
|
|
11
11
|
import * as v from "valibot";
|
|
12
12
|
//#region src/fetchers/set/item-locations.ts
|
|
@@ -19,6 +19,7 @@ async function fetchSetItemLocations(parameters, options) {
|
|
|
19
19
|
scopeUuids: setScopeUuids,
|
|
20
20
|
belongsToCollectionScopeUuids,
|
|
21
21
|
queries,
|
|
22
|
+
contentLanguage: getContentLanguage(options),
|
|
22
23
|
body: ({ items }) => ` let $propertyVariableUuids := (${Array.from(propertyVariableUuids, (uuid) => stringLiteral(uuid)).join(", ")})
|
|
23
24
|
|
|
24
25
|
return <items totalCount="{count(${items})}">{
|
|
@@ -48,8 +48,8 @@ function normalizePropertyValueDataType(dataType) {
|
|
|
48
48
|
function sortPropertyValues(values) {
|
|
49
49
|
return values.toSorted((a, b) => {
|
|
50
50
|
if (a.count !== b.count) return b.count - a.count;
|
|
51
|
-
const label = a.label?.getText() ?? null;
|
|
52
|
-
const otherLabel = b.label?.getText() ?? null;
|
|
51
|
+
const label = a.label?.getText(a.label.getDefaultLanguage()) ?? null;
|
|
52
|
+
const otherLabel = b.label?.getText(b.label.getDefaultLanguage()) ?? null;
|
|
53
53
|
if (label !== otherLabel) return label?.localeCompare(otherLabel ?? "") ?? 0;
|
|
54
54
|
return a.content?.toString().localeCompare(b.content?.toString() ?? "") ?? 0;
|
|
55
55
|
});
|
|
@@ -26,7 +26,7 @@ function parseWebsiteMetadata(data, options) {
|
|
|
26
26
|
const websiteTree = rawOchre.tree[0];
|
|
27
27
|
if (websiteTree == null) throw new Error("Website tree not found", { cause: data });
|
|
28
28
|
const identification = parseIdentification(websiteTree.identification, options);
|
|
29
|
-
const websiteName = identification.label.getText().trim();
|
|
29
|
+
const websiteName = identification.label.getText(identification.label.getDefaultLanguage()).trim();
|
|
30
30
|
const metadataDescription = (parseStringLike(rawOchre.metadata.description) ?? "").trim();
|
|
31
31
|
const properties = parseSimplifiedProperties(websiteTree.properties, options);
|
|
32
32
|
const reader = websitePresentationReader(properties);
|
package/dist/getters.mjs
CHANGED
|
@@ -27,7 +27,7 @@ function withDefaultOptions(options) {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
function getPropertyVariableLabel(property) {
|
|
30
|
-
return typeof property.variable.label === "string" ? property.variable.label : property.variable.label.getText();
|
|
30
|
+
return typeof property.variable.label === "string" ? property.variable.label : property.variable.label.getText(property.variable.label.getDefaultLanguage());
|
|
31
31
|
}
|
|
32
32
|
function hasPropertyValue(property, value) {
|
|
33
33
|
for (const candidateValue of property.values) if (deepEqual(candidateValue, value)) return true;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MultilingualOptions, MultilingualString, MultilingualStringEntries, MultilingualStringEntry, MultilingualStringInput, MultilingualStringJSON, MultilingualStringObject, MultilingualStringText } from "./parsers/multilingual.mjs";
|
|
2
|
-
import { AccordionWebBlock, ContextTree, ContextTreeFilterLevel, ContextTreeFilterVariant, ContextTreeLevel, ContextTreeLevelItem, ProtectedWebsite, ResponsiveStyles, Scope, Style, StylesheetCategory, StylesheetItem, WebAccordionItem, WebBlock, WebBlockByLayout, WebBlockItem, WebBlockLayout, WebElement, WebElementComponent, WebElementComponentName, WebElementComponentOf, WebElementOf, WebIiifViewer, WebImage, WebLoadingVariant, WebOptions, WebSectionDisplay, WebSectionVariant, WebSidebar, WebTitle, Webpage, Website, WebsiteMetadata, WebsitePrivacy, WebsitePropertyQuery, WebsitePropertyQueryNode, WebsiteSegment, WebsiteType } from "./types/website.mjs";
|
|
2
|
+
import { AccordionWebBlock, ContextTree, ContextTreeFilterLevel, ContextTreeFilterVariant, ContextTreeLevel, ContextTreeLevelItem, ProtectedWebsite, ResponsiveStyles, Scope, Style, StylesheetCategory, StylesheetItem, WebAccordionItem, WebBlock, WebBlockByLayout, WebBlockItem, WebBlockLayout, WebElement, WebElementComponent, WebElementComponentName, WebElementComponentOf, WebElementOf, WebIiifViewer, WebImage, WebLoadingVariant, WebOptions, WebRelationVariant, WebSectionDisplay, WebSectionVariant, WebSidebar, WebTitle, Webpage, Website, WebsiteMetadata, WebsitePrivacy, WebsitePropertyQuery, WebsitePropertyQueryNode, WebsiteSegment, WebsiteType } from "./types/website.mjs";
|
|
3
3
|
import { AnyBibliography, AnyConcept, AnyItem, AnyPeriod, AnyPerson, AnyPropertyValue, AnyPropertyVariable, AnyResource, AnySet, AnySpatialUnit, AnyText, AnyTree, BaseItem, BaseItemLink, BelongsTo, Bibliography, BibliographyEntryInfo, BibliographyItemLink, BibliographySourceDocument, Concept, ConceptItemLink, ContainedItemCategory, ContainedItemCategoryFromOption, ContainedItemCategoryOption, Context, ContextItem, ContextItemCategory, ContextNode, Coordinates, CoordinatesSource, DictionaryUnitItemLink, EmbeddedBibliography, EmbeddedConcept, EmbeddedItem, EmbeddedPeriod, EmbeddedPerson, EmbeddedPropertyValue, EmbeddedPropertyVariable, EmbeddedResource, EmbeddedSet, EmbeddedSpatialUnit, EmbeddedText, EmbeddedTree, Event, Gallery, Heading, HeadingItemCategory, Identification, Image, ImageMap, ImageMapArea, Interpretation, Item, ItemCategory, ItemCategoryFromOption, ItemCategoryOption, ItemCategoryWithEmbeddedItems, ItemContainerCategory, ItemLink, ItemLinkCategory, ItemLinks, ItemPayloadKind, ItemProperty, ItemWithoutEmbeddedItems, LanguageCodes, License, Metadata, Note, Observation, OcrString, Period, PeriodItemLink, Person, PersonItemLink, Property, PropertyLike, PropertyRelation, PropertyValue, PropertyValueContent, PropertyValueDataType, PropertyValueItemLink, PropertyValueQueryItem, PropertyVariable, PropertyVariableItemLink, Query, QueryGroup, QueryLeaf, QueryablePropertyValueDataType, RecursiveItemCategory, Resource, ResourceItemLink, Section, Set, SetAttributeValueQueryItem, SetBibliography, SetConcept, SetItem, SetItemCategory, SetItemLink, SetItemLocation, SetItemProperty, SetItemSimplifiedProperty, SetItemsSort, SetItemsSortDirection, SetPeriod, SetResource, SetSpatialUnit, SetStyle, SetStyleProperty, SetTree, SimplifiedProperty, SpatialUnit, SpatialUnitItemLink, Text, TextItemLink, TopLevelItem, Tree, TreeItemCategory, TreeItemLink } from "./types/index.mjs";
|
|
4
4
|
import { DEFAULT_PAGE_SIZE } from "./constants.mjs";
|
|
5
5
|
import { fetchGallery } from "./fetchers/gallery.mjs";
|
|
@@ -16,4 +16,4 @@ import { fetchWebsite } from "./fetchers/website.mjs";
|
|
|
16
16
|
import { PropertyOptions, PropertySelector, getLeafPropertyValues, getProperty, getPropertyValue, getPropertyValues, getUniqueProperties, getUniquePropertyVariableLabels, isPropertyMatchingFilter, normalizePropertyVariableLabel } from "./getters.mjs";
|
|
17
17
|
import { flattenItemProperties } from "./helpers.mjs";
|
|
18
18
|
import { defineLanguages } from "./parsers/languages.mjs";
|
|
19
|
-
export { type AccordionWebBlock, type AnyBibliography, type AnyConcept, type AnyItem, type AnyPeriod, type AnyPerson, type AnyPropertyValue, type AnyPropertyVariable, type AnyResource, type AnySet, type AnySpatialUnit, type AnyText, type AnyTree, type BaseItem, type BaseItemLink, type BelongsTo, type Bibliography, type BibliographyEntryInfo, type BibliographyItemLink, type BibliographySourceDocument, type Concept, type ConceptItemLink, type ContainedItemCategory, type ContainedItemCategoryFromOption, type ContainedItemCategoryOption, type Context, type ContextItem, type ContextItemCategory, type ContextNode, type ContextTree, type ContextTreeFilterLevel, type ContextTreeFilterVariant, type ContextTreeLevel, type ContextTreeLevelItem, type Coordinates, type CoordinatesSource, DEFAULT_PAGE_SIZE, type DictionaryUnitItemLink, type EmbeddedBibliography, type EmbeddedConcept, type EmbeddedItem, type EmbeddedPeriod, type EmbeddedPerson, type EmbeddedPropertyValue, type EmbeddedPropertyVariable, type EmbeddedResource, type EmbeddedSet, type EmbeddedSpatialUnit, type EmbeddedText, type EmbeddedTree, type Event, type Gallery, type Heading, type HeadingItemCategory, type Identification, type Image, type ImageMap, type ImageMapArea, type Interpretation, type Item, type ItemCategory, type ItemCategoryFromOption, type ItemCategoryOption, type ItemCategoryWithEmbeddedItems, type ItemContainerCategory, type ItemLink, type ItemLinkCategory, type ItemLinks, type ItemPayloadKind, type ItemProperty, type ItemWithoutEmbeddedItems, type LanguageCodes, type License, type Metadata, type MultilingualOptions, MultilingualString, type MultilingualStringEntries, type MultilingualStringEntry, type MultilingualStringInput, type MultilingualStringJSON, type MultilingualStringObject, type MultilingualStringText, type Note, type Observation, type OcrString, type Period, type PeriodItemLink, type Person, type PersonItemLink, type Property, type PropertyLike, type PropertyOptions, type PropertyRelation, type PropertySelector, type PropertyValue, type PropertyValueContent, type PropertyValueDataType, type PropertyValueItemLink, type PropertyValueQueryItem, type PropertyVariable, type PropertyVariableItemLink, type ProtectedWebsite, type Query, type QueryGroup, type QueryLeaf, type QueryablePropertyValueDataType, type RecursiveItemCategory, type Resource, type ResourceItemLink, type ResponsiveStyles, type Scope, type Section, type Set, type SetAttributeValueQueryItem, type SetBibliography, type SetConcept, type SetItem, type SetItemCategory, type SetItemLink, type SetItemLocation, type SetItemProperty, type SetItemSimplifiedProperty, type SetItemsSort, type SetItemsSortDirection, type SetPeriod, type SetResource, type SetSpatialUnit, type SetStyle, type SetStyleProperty, type SetTree, type SimplifiedProperty, type SpatialUnit, type SpatialUnitItemLink, type Style, type StylesheetCategory, type StylesheetItem, type Text, type TextItemLink, type TopLevelItem, type Tree, type TreeItemCategory, type TreeItemLink, type WebAccordionItem, type WebBlock, type WebBlockByLayout, type WebBlockItem, type WebBlockLayout, type WebElement, type WebElementComponent, type WebElementComponentName, type WebElementComponentOf, type WebElementOf, type WebIiifViewer, type WebImage, type WebLoadingVariant, type WebOptions, type WebSectionDisplay, type WebSectionVariant, type WebSidebar, type WebTitle, type Webpage, type Website, type WebsiteMetadata, type WebsitePrivacy, type WebsitePropertyQuery, type WebsitePropertyQueryNode, type WebsiteSegment, type WebsiteType, defineLanguages, fetchGallery, fetchItem, fetchItemChildren, fetchItemLinks, fetchItemOcrData, fetchSetItemLocations, fetchSetItems, fetchSetPropertyValues, fetchTreeItems, fetchWebsite, fetchWebsiteMetadata, flattenItemProperties, getLeafPropertyValues, getProperty, getPropertyValue, getPropertyValues, getUniqueProperties, getUniquePropertyVariableLabels, isPropertyMatchingFilter, normalizePropertyVariableLabel };
|
|
19
|
+
export { type AccordionWebBlock, type AnyBibliography, type AnyConcept, type AnyItem, type AnyPeriod, type AnyPerson, type AnyPropertyValue, type AnyPropertyVariable, type AnyResource, type AnySet, type AnySpatialUnit, type AnyText, type AnyTree, type BaseItem, type BaseItemLink, type BelongsTo, type Bibliography, type BibliographyEntryInfo, type BibliographyItemLink, type BibliographySourceDocument, type Concept, type ConceptItemLink, type ContainedItemCategory, type ContainedItemCategoryFromOption, type ContainedItemCategoryOption, type Context, type ContextItem, type ContextItemCategory, type ContextNode, type ContextTree, type ContextTreeFilterLevel, type ContextTreeFilterVariant, type ContextTreeLevel, type ContextTreeLevelItem, type Coordinates, type CoordinatesSource, DEFAULT_PAGE_SIZE, type DictionaryUnitItemLink, type EmbeddedBibliography, type EmbeddedConcept, type EmbeddedItem, type EmbeddedPeriod, type EmbeddedPerson, type EmbeddedPropertyValue, type EmbeddedPropertyVariable, type EmbeddedResource, type EmbeddedSet, type EmbeddedSpatialUnit, type EmbeddedText, type EmbeddedTree, type Event, type Gallery, type Heading, type HeadingItemCategory, type Identification, type Image, type ImageMap, type ImageMapArea, type Interpretation, type Item, type ItemCategory, type ItemCategoryFromOption, type ItemCategoryOption, type ItemCategoryWithEmbeddedItems, type ItemContainerCategory, type ItemLink, type ItemLinkCategory, type ItemLinks, type ItemPayloadKind, type ItemProperty, type ItemWithoutEmbeddedItems, type LanguageCodes, type License, type Metadata, type MultilingualOptions, MultilingualString, type MultilingualStringEntries, type MultilingualStringEntry, type MultilingualStringInput, type MultilingualStringJSON, type MultilingualStringObject, type MultilingualStringText, type Note, type Observation, type OcrString, type Period, type PeriodItemLink, type Person, type PersonItemLink, type Property, type PropertyLike, type PropertyOptions, type PropertyRelation, type PropertySelector, type PropertyValue, type PropertyValueContent, type PropertyValueDataType, type PropertyValueItemLink, type PropertyValueQueryItem, type PropertyVariable, type PropertyVariableItemLink, type ProtectedWebsite, type Query, type QueryGroup, type QueryLeaf, type QueryablePropertyValueDataType, type RecursiveItemCategory, type Resource, type ResourceItemLink, type ResponsiveStyles, type Scope, type Section, type Set, type SetAttributeValueQueryItem, type SetBibliography, type SetConcept, type SetItem, type SetItemCategory, type SetItemLink, type SetItemLocation, type SetItemProperty, type SetItemSimplifiedProperty, type SetItemsSort, type SetItemsSortDirection, type SetPeriod, type SetResource, type SetSpatialUnit, type SetStyle, type SetStyleProperty, type SetTree, type SimplifiedProperty, type SpatialUnit, type SpatialUnitItemLink, type Style, type StylesheetCategory, type StylesheetItem, type Text, type TextItemLink, type TopLevelItem, type Tree, type TreeItemCategory, type TreeItemLink, type WebAccordionItem, type WebBlock, type WebBlockByLayout, type WebBlockItem, type WebBlockLayout, type WebElement, type WebElementComponent, type WebElementComponentName, type WebElementComponentOf, type WebElementOf, type WebIiifViewer, type WebImage, type WebLoadingVariant, type WebOptions, type WebRelationVariant, type WebSectionDisplay, type WebSectionVariant, type WebSidebar, type WebTitle, type Webpage, type Website, type WebsiteMetadata, type WebsitePrivacy, type WebsitePropertyQuery, type WebsitePropertyQueryNode, type WebsiteSegment, type WebsiteType, defineLanguages, fetchGallery, fetchItem, fetchItemChildren, fetchItemLinks, fetchItemOcrData, fetchSetItemLocations, fetchSetItems, fetchSetPropertyValues, fetchTreeItems, fetchWebsite, fetchWebsiteMetadata, flattenItemProperties, getLeafPropertyValues, getProperty, getPropertyValue, getPropertyValues, getUniqueProperties, getUniquePropertyVariableLabels, isPropertyMatchingFilter, normalizePropertyVariableLabel };
|
|
@@ -5,6 +5,17 @@ import { XMLContent, XMLString } from "../xml/types.mjs";
|
|
|
5
5
|
export type FetchFunction = (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
|
|
6
6
|
export type FetchBaseOptions<TLanguages extends ReadonlyArray<string> | undefined = undefined> = {
|
|
7
7
|
languages?: TLanguages;
|
|
8
|
+
/**
|
|
9
|
+
* Fetch only this language of every multilingual field, instead of every
|
|
10
|
+
* language OCHRE holds. A field without it keeps its `fallbackLanguage`
|
|
11
|
+
* content, else its content in any language; aliases are always kept.
|
|
12
|
+
*/
|
|
13
|
+
language?: string;
|
|
14
|
+
/**
|
|
15
|
+
* The language shown for fields that have no `language` content, typically
|
|
16
|
+
* the website's default language (defaults to `"eng"`)
|
|
17
|
+
*/
|
|
18
|
+
fallbackLanguage?: string;
|
|
8
19
|
fetch?: FetchFunction;
|
|
9
20
|
signal?: AbortSignal;
|
|
10
21
|
timeoutMilliseconds?: number;
|
package/dist/parsers/helpers.mjs
CHANGED
|
@@ -58,15 +58,19 @@ function parseRequiredContentLike(value, options) {
|
|
|
58
58
|
return parseContentLike(value, options) ?? MultilingualString.empty(options.languages, { defaultLanguage: options.defaultLanguage });
|
|
59
59
|
}
|
|
60
60
|
function parseContentLikeText(value, options) {
|
|
61
|
-
|
|
61
|
+
const content = parseContentLike(value, options);
|
|
62
|
+
return content?.getText(content.getDefaultLanguage()).trim() ?? "";
|
|
62
63
|
}
|
|
63
64
|
function parseStringContent(value, options = FALLBACK_PARSER_OPTIONS) {
|
|
64
65
|
if (value == null) return "";
|
|
65
66
|
if (typeof value === "string") return value;
|
|
66
|
-
if (isXMLContent(value))
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
if (isXMLContent(value)) {
|
|
68
|
+
const content = parseXMLContent(value, {
|
|
69
|
+
languages: options.languages,
|
|
70
|
+
defaultLanguage: options.defaultLanguage
|
|
71
|
+
});
|
|
72
|
+
return content.getText(content.getDefaultLanguage());
|
|
73
|
+
}
|
|
70
74
|
return parseXMLString(value).text;
|
|
71
75
|
}
|
|
72
76
|
//#endregion
|
package/dist/parsers/index.mjs
CHANGED
|
@@ -457,7 +457,7 @@ function parsePropertyDataType(dataType) {
|
|
|
457
457
|
function parsePropertyValueContent(value, options) {
|
|
458
458
|
const dataType = parsePropertyDataType(value.dataType);
|
|
459
459
|
const rawLabel = value.content != null ? parseRequiredContentLike(value, options) : value.payload != null && value.payload !== "" ? multilingualFromText(value.payload, options) : null;
|
|
460
|
-
const contentText = readPropertyValueText(value, () => rawLabel?.getText() ?? null);
|
|
460
|
+
const contentText = readPropertyValueText(value, () => rawLabel?.getText(rawLabel.getDefaultLanguage()) ?? null);
|
|
461
461
|
const common = {
|
|
462
462
|
hierarchy: {
|
|
463
463
|
isLeaf: value.inherited == null || !value.inherited,
|
|
@@ -109,11 +109,11 @@ export declare class MultilingualString<T extends ReadonlyArray<string> = Readon
|
|
|
109
109
|
/**
|
|
110
110
|
* Get text in a specific language, falling back when it has none
|
|
111
111
|
*/
|
|
112
|
-
getText(language
|
|
112
|
+
getText(language: T[number]): string;
|
|
113
113
|
/**
|
|
114
114
|
* Get rich text in a specific language, falling back when it has none
|
|
115
115
|
*/
|
|
116
|
-
getRichText(language
|
|
116
|
+
getRichText(language: T[number]): string;
|
|
117
117
|
/**
|
|
118
118
|
* Get text in a specific language, with no fallback
|
|
119
119
|
*/
|
|
@@ -133,16 +133,16 @@ export declare class MultilingualString<T extends ReadonlyArray<string> = Readon
|
|
|
133
133
|
* can carry several entries for one language, and this returns all of them
|
|
134
134
|
* rather than only the primary. Each entry carries both `text` and
|
|
135
135
|
* `richText`, so a caller rendering a rich field reads them from here.
|
|
136
|
-
* @param language - The language to read
|
|
136
|
+
* @param language - The language to read
|
|
137
137
|
* @returns The entries, or an empty array when no language has any
|
|
138
138
|
*/
|
|
139
|
-
getEntries(language
|
|
139
|
+
getEntries(language: T[number]): Array<MultilingualStringEntry>;
|
|
140
140
|
/**
|
|
141
141
|
* Get the text of every entry for a language, falling back when it has none
|
|
142
|
-
* @param language - The language to read
|
|
142
|
+
* @param language - The language to read
|
|
143
143
|
* @returns The texts, or an empty array when no language has any
|
|
144
144
|
*/
|
|
145
|
-
getTexts(language
|
|
145
|
+
getTexts(language: T[number]): Array<string>;
|
|
146
146
|
/**
|
|
147
147
|
* Get the text of every entry for a language, with no fallback
|
|
148
148
|
* @param language - The language to read
|
|
@@ -259,10 +259,6 @@ export declare class MultilingualString<T extends ReadonlyArray<string> = Readon
|
|
|
259
259
|
* @returns A new multilingual string
|
|
260
260
|
*/
|
|
261
261
|
filterEntries(shouldKeep: (entry: MultilingualStringEntry, language: T[number]) => boolean): MultilingualString<T>;
|
|
262
|
-
/**
|
|
263
|
-
* Get the string representation, using the default language
|
|
264
|
-
*/
|
|
265
|
-
toString(): string;
|
|
266
262
|
/**
|
|
267
263
|
* Get the JSON representation
|
|
268
264
|
*
|
|
@@ -228,7 +228,7 @@ var MultilingualString = class MultilingualString {
|
|
|
228
228
|
* can carry several entries for one language, and this returns all of them
|
|
229
229
|
* rather than only the primary. Each entry carries both `text` and
|
|
230
230
|
* `richText`, so a caller rendering a rich field reads them from here.
|
|
231
|
-
* @param language - The language to read
|
|
231
|
+
* @param language - The language to read
|
|
232
232
|
* @returns The entries, or an empty array when no language has any
|
|
233
233
|
*/
|
|
234
234
|
getEntries(language) {
|
|
@@ -236,7 +236,7 @@ var MultilingualString = class MultilingualString {
|
|
|
236
236
|
}
|
|
237
237
|
/**
|
|
238
238
|
* Get the text of every entry for a language, falling back when it has none
|
|
239
|
-
* @param language - The language to read
|
|
239
|
+
* @param language - The language to read
|
|
240
240
|
* @returns The texts, or an empty array when no language has any
|
|
241
241
|
*/
|
|
242
242
|
getTexts(language) {
|
|
@@ -423,12 +423,6 @@ var MultilingualString = class MultilingualString {
|
|
|
423
423
|
}, newAvailableLanguages);
|
|
424
424
|
}
|
|
425
425
|
/**
|
|
426
|
-
* Get the string representation, using the default language
|
|
427
|
-
*/
|
|
428
|
-
toString() {
|
|
429
|
-
return this.getText();
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
426
|
* Get the JSON representation
|
|
433
427
|
*
|
|
434
428
|
* Carries `defaultLanguage`, because it is not derivable from the content:
|
|
@@ -85,6 +85,23 @@ function parseCollectionDisplayedProperties(reader) {
|
|
|
85
85
|
label: value.label
|
|
86
86
|
}]);
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Parses the "customize-special" "relations" properties, each pairing the
|
|
90
|
+
* property variables it links with "use-property" with a display "variant".
|
|
91
|
+
*/
|
|
92
|
+
function parseCollectionRelations(reader) {
|
|
93
|
+
const relations = [];
|
|
94
|
+
const relationReaders = reader.nestedAllByValue("customize-special", "relations");
|
|
95
|
+
for (const relationReader of relationReaders) {
|
|
96
|
+
const variant = relationReader.value("variant");
|
|
97
|
+
const properties = parseCollectionDisplayedProperties(relationReader) ?? [];
|
|
98
|
+
for (const property of properties) relations.push({
|
|
99
|
+
...property,
|
|
100
|
+
variant
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return relations;
|
|
104
|
+
}
|
|
88
105
|
const THREE_D_VIEWER_COMPONENT_DEFAULTS = {
|
|
89
106
|
isInteractive: true,
|
|
90
107
|
isControlsDisplayed: true
|
|
@@ -261,6 +278,7 @@ function parseCollectionComponent(parameters) {
|
|
|
261
278
|
component: "collection",
|
|
262
279
|
linkUuids: setLinks.map((link) => link.uuid),
|
|
263
280
|
displayedProperties: parseCollectionDisplayedProperties(componentReader),
|
|
281
|
+
relations: parseCollectionRelations(componentReader),
|
|
264
282
|
...COLLECTION_PROPERTY_DEFAULTS,
|
|
265
283
|
...propertyOverrides,
|
|
266
284
|
image: {
|
|
@@ -472,9 +490,12 @@ function parseQueryComponent(parameters) {
|
|
|
472
490
|
}
|
|
473
491
|
if (items.length === 0) throw new Error(formatComponentError("No queries found", "query", elementResource), { cause: componentProperty });
|
|
474
492
|
const componentOptions = parseWebsiteOptions(elementResource.options, options);
|
|
475
|
-
const
|
|
493
|
+
const overrideReader = componentReader.nestedByValue("sub-component-override", "collection");
|
|
494
|
+
const collectionProperties = parseCollectionPropertyOverrides(overrideReader);
|
|
476
495
|
const displayedProperties = parseCollectionDisplayedProperties(componentReader);
|
|
477
496
|
if (displayedProperties != null) collectionProperties.displayedProperties = displayedProperties;
|
|
497
|
+
const relations = parseCollectionRelations(overrideReader);
|
|
498
|
+
if (relations.length > 0) collectionProperties.relations = relations;
|
|
478
499
|
return {
|
|
479
500
|
component: "query",
|
|
480
501
|
linkUuids: setLinks.map((link) => link.uuid),
|
|
@@ -104,7 +104,7 @@ function parseWebsiteOptions(rawOptions, options) {
|
|
|
104
104
|
};
|
|
105
105
|
const notes = parseNotes(rawOptions?.notes, options);
|
|
106
106
|
for (const note of notes) {
|
|
107
|
-
if (note.title?.getText() !== "Title label") continue;
|
|
107
|
+
if (note.title?.getText(note.title.getDefaultLanguage()) !== "Title label") continue;
|
|
108
108
|
parsedOptions.labels.title = note.content;
|
|
109
109
|
break;
|
|
110
110
|
}
|
|
@@ -76,6 +76,7 @@ function parseWebsiteProperties(properties, websiteTree, sidebar, options, paren
|
|
|
76
76
|
isPersistentIdentifierDisplayed: parent?.itemPage.isPersistentIdentifierDisplayed ?? true,
|
|
77
77
|
iiifViewer: parent?.itemPage.iiifViewer ?? "universal-viewer"
|
|
78
78
|
},
|
|
79
|
+
relations: { variant: parent?.relations.variant ?? "list" },
|
|
79
80
|
options: {
|
|
80
81
|
contextTree: parent?.options.contextTree ?? null,
|
|
81
82
|
scopes: parent?.options.scopes ?? null,
|
|
@@ -117,7 +118,7 @@ function parseWebsiteProperties(properties, websiteTree, sidebar, options, paren
|
|
|
117
118
|
websiteReader.readAllUuids(returnProperties.navbar, { searchBarBoundElementUuid: "bound-element-navbar-search-bar" });
|
|
118
119
|
websiteReader.readAll(returnProperties.footer, { isDisplayed: "footer-displayed" });
|
|
119
120
|
websiteReader.readAllUuids(returnProperties.footer, { logoUuid: "footer-logo" });
|
|
120
|
-
const itemPageReader = websiteReader.nestedByValue("
|
|
121
|
+
const itemPageReader = websiteReader.nestedByValue("customize-special", "item-page");
|
|
121
122
|
if (itemPageReader.size > 0) {
|
|
122
123
|
for (const [key, slug] of [
|
|
123
124
|
["description", "description"],
|
|
@@ -142,6 +143,7 @@ function parseWebsiteProperties(properties, websiteTree, sidebar, options, paren
|
|
|
142
143
|
iiifViewer: "item-page-iiif-viewer"
|
|
143
144
|
});
|
|
144
145
|
}
|
|
146
|
+
websiteReader.nestedByValue("customize-special", "relations").readAll(returnProperties.relations, { variant: "variant" });
|
|
145
147
|
if (websiteTree.options != null) {
|
|
146
148
|
const parsedOptions = parseWebsiteOptions(websiteTree.options, options);
|
|
147
149
|
returnProperties.options.scopes = (parsedOptions.scopes != null && parsedOptions.scopes.length > 0 ? parsedOptions : returnProperties.options).scopes;
|
|
@@ -70,6 +70,7 @@ export declare class WebsitePresentationReader<T extends LanguageCodes> {
|
|
|
70
70
|
readAllUuids<O extends object>(target: O, labels: Partial<Record<UuidKeys<O>, string>>): void;
|
|
71
71
|
nested(label: string): WebsitePresentationReader<T>;
|
|
72
72
|
nestedByValue(label: string, value: WebsitePropertyContent<T>): WebsitePresentationReader<T>;
|
|
73
|
+
nestedAllByValue(label: string, value: WebsitePropertyContent<T>): Array<WebsitePresentationReader<T>>;
|
|
73
74
|
get size(): number;
|
|
74
75
|
}
|
|
75
76
|
export declare function websitePresentationReader<T extends LanguageCodes>(properties: ReadonlyArray<SimplifiedProperty<T>>): WebsitePresentationReader<T>;
|
|
@@ -103,6 +103,14 @@ var WebsitePresentationReader = class WebsitePresentationReader {
|
|
|
103
103
|
nestedByValue(label, value) {
|
|
104
104
|
return new WebsitePresentationReader(this.propertyByValue(label, value)?.properties ?? []);
|
|
105
105
|
}
|
|
106
|
+
nestedAllByValue(label, value) {
|
|
107
|
+
const readers = [];
|
|
108
|
+
for (const property of this.sourceProperties) if (getProperty([property], {
|
|
109
|
+
label,
|
|
110
|
+
valueContent: value
|
|
111
|
+
}) != null) readers.push(new WebsitePresentationReader(property.properties));
|
|
112
|
+
return readers;
|
|
113
|
+
}
|
|
106
114
|
get size() {
|
|
107
115
|
return this.sourceProperties.length;
|
|
108
116
|
}
|
package/dist/query.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PropertyRelation, Query } from "./types/index.mjs";
|
|
2
|
-
import { OchreQueryContext } from "./xquery.mjs";
|
|
2
|
+
import { OchreContentLanguage, OchreQueryContext } from "./xquery.mjs";
|
|
3
3
|
//#region src/query.d.ts
|
|
4
4
|
export declare function buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids: ReadonlyArray<string>, belongsToCollectionPropertyVariableUuid: string): string | null;
|
|
5
5
|
/**
|
|
@@ -75,6 +75,7 @@ export type ItemsContainer = keyof typeof ITEMS_CONTAINERS;
|
|
|
75
75
|
* @param parameters.queries - The query tree to compile, or null to match every item
|
|
76
76
|
* @param parameters.declarations - Extra prolog declarations, placed before the compiled prolog
|
|
77
77
|
* @param parameters.body - Builds the body from the name of the variable holding the items
|
|
78
|
+
* @param parameters.contentLanguage - Keep only this language of every multilingual field in the result
|
|
78
79
|
* @returns A complete XQuery document
|
|
79
80
|
* @internal
|
|
80
81
|
*/
|
|
@@ -87,6 +88,7 @@ export declare function compileContainerItemsQuery(parameters: {
|
|
|
87
88
|
body: (context: OchreQueryContext & {
|
|
88
89
|
items: string;
|
|
89
90
|
}) => string;
|
|
91
|
+
contentLanguage?: OchreContentLanguage | null;
|
|
90
92
|
}): string;
|
|
91
93
|
/**
|
|
92
94
|
* Reduce a property-value facet query tree to the leaves that filter items
|
package/dist/query.mjs
CHANGED
|
@@ -14,32 +14,25 @@ const CTS_INCLUDES_STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
14
14
|
const CTS_INCLUDES_TOKEN_WORD_REGEX = /^\p{L}+$/u;
|
|
15
15
|
const CTS_INCLUDES_TOKEN_REGEX = /[\p{L}\p{N}*?]+/gu;
|
|
16
16
|
const CTS_EXACT_TEXT_TOKEN_REGEX = /[\p{L}\p{N}]+/gu;
|
|
17
|
-
const
|
|
18
|
-
title: [
|
|
19
|
-
|
|
20
|
-
"label",
|
|
21
|
-
"content"
|
|
22
|
-
],
|
|
23
|
-
description: ["description", "content"],
|
|
17
|
+
const CONTENT_TARGET_FIELD_ELEMENT_PATHS = {
|
|
18
|
+
title: ["identification", "label"],
|
|
19
|
+
description: ["description"],
|
|
24
20
|
image: [
|
|
25
21
|
"image",
|
|
26
22
|
"identification",
|
|
27
|
-
"label"
|
|
28
|
-
"content"
|
|
23
|
+
"label"
|
|
29
24
|
],
|
|
30
25
|
periods: [
|
|
31
26
|
"periods",
|
|
32
27
|
"period",
|
|
33
28
|
"identification",
|
|
34
|
-
"label"
|
|
35
|
-
"content"
|
|
29
|
+
"label"
|
|
36
30
|
],
|
|
37
31
|
bibliography: [
|
|
38
32
|
"bibliographies",
|
|
39
33
|
"bibliography",
|
|
40
34
|
"identification",
|
|
41
|
-
"label"
|
|
42
|
-
"content"
|
|
35
|
+
"label"
|
|
43
36
|
]
|
|
44
37
|
};
|
|
45
38
|
const HELPER_SEARCH_VALUE_REFERENCE = "$value";
|
|
@@ -246,36 +239,51 @@ function buildValueNotIdReferenceQuery() {
|
|
|
246
239
|
value: searchValue("IDREF")
|
|
247
240
|
}));
|
|
248
241
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
242
|
+
/**
|
|
243
|
+
* Match a field's `content` in a language, falling back to another language
|
|
244
|
+
* only for fields that carry no content in the first one
|
|
245
|
+
*
|
|
246
|
+
* Placed inside the field element (a label, a note, a property value), so the
|
|
247
|
+
* missing-language check is scoped to that one field. Without a distinct
|
|
248
|
+
* fallback it is the single-language query this always compiled to.
|
|
249
|
+
*/
|
|
250
|
+
function buildLocalizedContentQueryExpression(parameters) {
|
|
251
|
+
const { language, fallbackLanguage, buildMatchQuery } = parameters;
|
|
252
|
+
function buildContentQuery(contentLanguage) {
|
|
253
|
+
return buildNestedElementQuery(["content"], buildAndCtsQueryExpressionInternal([buildContentLanguageQuery(contentLanguage), buildMatchQuery(contentLanguage)]));
|
|
254
|
+
}
|
|
255
|
+
if (fallbackLanguage == null || fallbackLanguage === language) return buildContentQuery(language);
|
|
256
|
+
return buildOrCtsQueryExpressionInternal([buildContentQuery(language), buildAndCtsQueryExpressionInternal([buildNotCtsQueryExpression(buildNestedElementQuery(["content"], buildContentLanguageQuery(language))), buildContentQuery(fallbackLanguage)])]);
|
|
262
257
|
}
|
|
263
|
-
function
|
|
264
|
-
const {
|
|
265
|
-
return
|
|
258
|
+
function buildRichTextContentQueryExpression(parameters) {
|
|
259
|
+
const { value, matchMode, isCaseSensitive, language, fallbackLanguage } = parameters;
|
|
260
|
+
return buildLocalizedContentQueryExpression({
|
|
266
261
|
language,
|
|
267
|
-
|
|
268
|
-
matchMode
|
|
269
|
-
|
|
270
|
-
|
|
262
|
+
fallbackLanguage,
|
|
263
|
+
buildMatchQuery: (contentLanguage) => matchMode === "exact" ? buildRichTextExactQueryExpression({
|
|
264
|
+
value,
|
|
265
|
+
isCaseSensitive,
|
|
266
|
+
language: contentLanguage
|
|
267
|
+
}) : buildCtsWordQueryExpression({
|
|
268
|
+
value,
|
|
269
|
+
matchMode,
|
|
270
|
+
isCaseSensitive,
|
|
271
|
+
queryFamily: "text",
|
|
272
|
+
language: contentLanguage
|
|
273
|
+
})
|
|
274
|
+
});
|
|
271
275
|
}
|
|
272
276
|
function buildValueContentExactInnerQuery(parameters) {
|
|
273
|
-
const { language, value, isCaseSensitive } = parameters;
|
|
274
|
-
return
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
277
|
+
const { language, fallbackLanguage, value, isCaseSensitive } = parameters;
|
|
278
|
+
return buildLocalizedContentQueryExpression({
|
|
279
|
+
language,
|
|
280
|
+
fallbackLanguage,
|
|
281
|
+
buildMatchQuery: () => buildCtsElementValueQueryExpression({
|
|
282
|
+
elementName: "string",
|
|
283
|
+
value,
|
|
284
|
+
isCaseSensitive
|
|
285
|
+
})
|
|
286
|
+
});
|
|
279
287
|
}
|
|
280
288
|
function buildValueDirectTextInnerQuery(parameters) {
|
|
281
289
|
const { value, matchMode, isCaseSensitive } = parameters;
|
|
@@ -310,26 +318,23 @@ function buildValueRawValueInnerQuery(parameters) {
|
|
|
310
318
|
});
|
|
311
319
|
}
|
|
312
320
|
function buildNotesQueryExpression(parameters) {
|
|
313
|
-
const { value, matchMode, isCaseSensitive, language } = parameters;
|
|
314
|
-
return buildNestedElementQuery([
|
|
315
|
-
"notes",
|
|
316
|
-
"note",
|
|
317
|
-
"content"
|
|
318
|
-
], buildRichTextContentQueryExpression({
|
|
321
|
+
const { value, matchMode, isCaseSensitive, language, fallbackLanguage } = parameters;
|
|
322
|
+
return buildNestedElementQuery(["notes", "note"], buildRichTextContentQueryExpression({
|
|
319
323
|
value,
|
|
320
324
|
matchMode,
|
|
321
325
|
isCaseSensitive,
|
|
322
|
-
language
|
|
326
|
+
language,
|
|
327
|
+
fallbackLanguage
|
|
323
328
|
}));
|
|
324
329
|
}
|
|
325
330
|
function buildContentTargetQueryExpression(parameters) {
|
|
326
|
-
const { target, value, matchMode, isCaseSensitive, language } = parameters;
|
|
327
|
-
|
|
328
|
-
return buildNestedElementQuery(contentElementPath, buildRichTextContentQueryExpression({
|
|
331
|
+
const { target, value, matchMode, isCaseSensitive, language, fallbackLanguage } = parameters;
|
|
332
|
+
return buildNestedElementQuery(CONTENT_TARGET_FIELD_ELEMENT_PATHS[target], buildRichTextContentQueryExpression({
|
|
329
333
|
value,
|
|
330
334
|
matchMode,
|
|
331
335
|
isCaseSensitive,
|
|
332
|
-
language
|
|
336
|
+
language,
|
|
337
|
+
fallbackLanguage
|
|
333
338
|
}));
|
|
334
339
|
}
|
|
335
340
|
function buildPropertyQueryExpression(parameters) {
|
|
@@ -381,16 +386,18 @@ function buildPropertyPresenceQueryExpression(parameters) {
|
|
|
381
386
|
});
|
|
382
387
|
}
|
|
383
388
|
function buildPropertyStringQueryExpression(parameters) {
|
|
384
|
-
const { propertyVariable, propertyRelation, value, matchMode, isCaseSensitive, language } = parameters;
|
|
389
|
+
const { propertyVariable, propertyRelation, value, matchMode, isCaseSensitive, language, fallbackLanguage } = parameters;
|
|
385
390
|
return buildPropertyTextMatchQueryExpression({
|
|
386
391
|
propertyVariable,
|
|
387
392
|
propertyRelation,
|
|
388
393
|
contentQueryExpression: matchMode === "exact" ? buildValueContentExactInnerQuery({
|
|
389
394
|
language,
|
|
395
|
+
fallbackLanguage,
|
|
390
396
|
value,
|
|
391
397
|
isCaseSensitive
|
|
392
|
-
}) :
|
|
398
|
+
}) : buildRichTextContentQueryExpression({
|
|
393
399
|
language,
|
|
400
|
+
fallbackLanguage,
|
|
394
401
|
value,
|
|
395
402
|
matchMode,
|
|
396
403
|
isCaseSensitive
|
|
@@ -429,8 +436,9 @@ function buildPropertyAllQueryExpression(parameters) {
|
|
|
429
436
|
propertyVariable: query.propertyVariable,
|
|
430
437
|
propertyRelation: query.propertyRelation,
|
|
431
438
|
valueFilters: [buildValueNotIdReferenceQuery()],
|
|
432
|
-
contentQueryExpression:
|
|
439
|
+
contentQueryExpression: buildRichTextContentQueryExpression({
|
|
433
440
|
language: query.language,
|
|
441
|
+
fallbackLanguage: query.fallbackLanguage,
|
|
434
442
|
value,
|
|
435
443
|
matchMode,
|
|
436
444
|
isCaseSensitive: query.isCaseSensitive
|
|
@@ -470,18 +478,20 @@ function buildPropertyDateRangeQueryExpression(query) {
|
|
|
470
478
|
});
|
|
471
479
|
}
|
|
472
480
|
function buildItemStringQueryExpression(parameters) {
|
|
473
|
-
const { value, matchMode, isCaseSensitive, language } = parameters;
|
|
481
|
+
const { value, matchMode, isCaseSensitive, language, fallbackLanguage } = parameters;
|
|
474
482
|
return buildOrCtsQueryExpressionInternal([buildContentTargetQueryExpression({
|
|
475
483
|
target: "title",
|
|
476
484
|
value,
|
|
477
485
|
matchMode,
|
|
478
486
|
isCaseSensitive,
|
|
479
|
-
language
|
|
487
|
+
language,
|
|
488
|
+
fallbackLanguage
|
|
480
489
|
}), buildPropertyStringQueryExpression({
|
|
481
490
|
value,
|
|
482
491
|
matchMode,
|
|
483
492
|
isCaseSensitive,
|
|
484
|
-
language
|
|
493
|
+
language,
|
|
494
|
+
fallbackLanguage
|
|
485
495
|
})]);
|
|
486
496
|
}
|
|
487
497
|
function tokenizeOcrExactValue(value) {
|
|
@@ -614,13 +624,15 @@ function buildLeafValueQueryExpression(parameters) {
|
|
|
614
624
|
value,
|
|
615
625
|
matchMode,
|
|
616
626
|
isCaseSensitive: query.isCaseSensitive,
|
|
617
|
-
language: query.language
|
|
627
|
+
language: query.language,
|
|
628
|
+
fallbackLanguage: query.fallbackLanguage
|
|
618
629
|
});
|
|
619
630
|
case "notes": return buildNotesQueryExpression({
|
|
620
631
|
value,
|
|
621
632
|
matchMode,
|
|
622
633
|
isCaseSensitive: query.isCaseSensitive,
|
|
623
|
-
language: query.language
|
|
634
|
+
language: query.language,
|
|
635
|
+
fallbackLanguage: query.fallbackLanguage
|
|
624
636
|
});
|
|
625
637
|
case "title":
|
|
626
638
|
case "description":
|
|
@@ -631,7 +643,8 @@ function buildLeafValueQueryExpression(parameters) {
|
|
|
631
643
|
value,
|
|
632
644
|
matchMode,
|
|
633
645
|
isCaseSensitive: query.isCaseSensitive,
|
|
634
|
-
language: query.language
|
|
646
|
+
language: query.language,
|
|
647
|
+
fallbackLanguage: query.fallbackLanguage
|
|
635
648
|
});
|
|
636
649
|
case "property": switch (query.dataType) {
|
|
637
650
|
case "all": return buildPropertyAllQueryExpression({
|
|
@@ -650,7 +663,8 @@ function buildLeafValueQueryExpression(parameters) {
|
|
|
650
663
|
value,
|
|
651
664
|
matchMode,
|
|
652
665
|
isCaseSensitive: query.isCaseSensitive,
|
|
653
|
-
language: query.language
|
|
666
|
+
language: query.language,
|
|
667
|
+
fallbackLanguage: query.fallbackLanguage
|
|
654
668
|
});
|
|
655
669
|
case "integer":
|
|
656
670
|
case "decimal":
|
|
@@ -728,7 +742,8 @@ function getLeafHelperKey(parameters) {
|
|
|
728
742
|
query.target,
|
|
729
743
|
value,
|
|
730
744
|
query.isCaseSensitive ? "case-sensitive" : "case-insensitive",
|
|
731
|
-
query.language
|
|
745
|
+
query.language,
|
|
746
|
+
query.fallbackLanguage ?? ""
|
|
732
747
|
].join("|");
|
|
733
748
|
case "property": return [
|
|
734
749
|
"leaf",
|
|
@@ -739,7 +754,8 @@ function getLeafHelperKey(parameters) {
|
|
|
739
754
|
query.propertyRelation ?? "",
|
|
740
755
|
value,
|
|
741
756
|
query.isCaseSensitive ? "case-sensitive" : "case-insensitive",
|
|
742
|
-
query.language
|
|
757
|
+
query.language,
|
|
758
|
+
query.fallbackLanguage ?? ""
|
|
743
759
|
].join("|");
|
|
744
760
|
}
|
|
745
761
|
}
|
|
@@ -775,6 +791,7 @@ function getIncludesLeafHelperKey(parameters) {
|
|
|
775
791
|
query.target,
|
|
776
792
|
query.isCaseSensitive ? "case-sensitive" : "case-insensitive",
|
|
777
793
|
query.language,
|
|
794
|
+
query.fallbackLanguage ?? "",
|
|
778
795
|
isWildcarded ? "wildcarded" : "unwildcarded",
|
|
779
796
|
isStemmed ? "stemmed" : "unstemmed"
|
|
780
797
|
].join("|");
|
|
@@ -786,6 +803,7 @@ function getIncludesLeafHelperKey(parameters) {
|
|
|
786
803
|
query.propertyRelation ?? "",
|
|
787
804
|
query.isCaseSensitive ? "case-sensitive" : "case-insensitive",
|
|
788
805
|
query.language,
|
|
806
|
+
query.fallbackLanguage ?? "",
|
|
789
807
|
isWildcarded ? "wildcarded" : "unwildcarded",
|
|
790
808
|
isStemmed ? "stemmed" : "unstemmed"
|
|
791
809
|
].join("|");
|
|
@@ -876,7 +894,7 @@ function getCompatibleIncludesGroupLeaves(query) {
|
|
|
876
894
|
if (!("or" in query) || query.or.length <= 1) return null;
|
|
877
895
|
const leafQueries = [];
|
|
878
896
|
for (const childQuery of query.or) {
|
|
879
|
-
if (!isQueryLeaf(childQuery) || childQuery.target === "ocr") return null;
|
|
897
|
+
if (!isQueryLeaf(childQuery) || childQuery.target === "ocr" || childQuery.fallbackLanguage != null && childQuery.fallbackLanguage !== childQuery.language) return null;
|
|
880
898
|
leafQueries.push(childQuery);
|
|
881
899
|
}
|
|
882
900
|
const firstQuery = leafQueries[0];
|
|
@@ -941,7 +959,26 @@ function buildCtsItemsPlan(queryExpression) {
|
|
|
941
959
|
return {
|
|
942
960
|
kind: "search",
|
|
943
961
|
itemPredicates: [],
|
|
944
|
-
queryExpressions: [queryExpression]
|
|
962
|
+
queryExpressions: [queryExpression],
|
|
963
|
+
resultPredicates: []
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Plan a leaf that falls back to a second language for fields missing the first
|
|
968
|
+
*
|
|
969
|
+
* The strict query needs a `cts:not-query` scoped to one field, which CTS
|
|
970
|
+
* resolves against the Set's single fragment and so rules out every item the
|
|
971
|
+
* moment any item holds the first language. The search therefore runs a
|
|
972
|
+
* superset (the leaf in either language), and `cts:contains` then applies the
|
|
973
|
+
* strict query to each matched item projection, where the negation is exact.
|
|
974
|
+
*/
|
|
975
|
+
function buildFallbackItemsPlan(parameters) {
|
|
976
|
+
const { supersetQueryExpression, strictQueryExpression } = parameters;
|
|
977
|
+
return {
|
|
978
|
+
kind: "search",
|
|
979
|
+
itemPredicates: [],
|
|
980
|
+
queryExpressions: [supersetQueryExpression],
|
|
981
|
+
resultPredicates: [`[cts:contains(., ${strictQueryExpression})]`]
|
|
945
982
|
};
|
|
946
983
|
}
|
|
947
984
|
/**
|
|
@@ -958,7 +995,8 @@ function buildNegatedItemsPlan(queryExpression) {
|
|
|
958
995
|
return {
|
|
959
996
|
kind: "search",
|
|
960
997
|
itemPredicates: [`[not(cts:contains(., ${queryExpression}))]`],
|
|
961
|
-
queryExpressions: []
|
|
998
|
+
queryExpressions: [],
|
|
999
|
+
resultPredicates: []
|
|
962
1000
|
};
|
|
963
1001
|
}
|
|
964
1002
|
/**
|
|
@@ -976,6 +1014,9 @@ function flattenItemsPlans(childPlans, kind) {
|
|
|
976
1014
|
}
|
|
977
1015
|
return flattenedPlans;
|
|
978
1016
|
}
|
|
1017
|
+
function appendUniquePredicates(predicates, addedPredicates) {
|
|
1018
|
+
for (const predicate of addedPredicates) if (!predicates.includes(predicate)) predicates.push(predicate);
|
|
1019
|
+
}
|
|
979
1020
|
/**
|
|
980
1021
|
* Fold the children of an `and` group into one plan
|
|
981
1022
|
*
|
|
@@ -987,7 +1028,8 @@ function buildAndItemsPlan(childPlans) {
|
|
|
987
1028
|
const mergedPlan = {
|
|
988
1029
|
kind: "search",
|
|
989
1030
|
itemPredicates: [],
|
|
990
|
-
queryExpressions: []
|
|
1031
|
+
queryExpressions: [],
|
|
1032
|
+
resultPredicates: []
|
|
991
1033
|
};
|
|
992
1034
|
const unfoldablePlans = [];
|
|
993
1035
|
for (const childPlan of flattenItemsPlans(childPlans, "intersect")) {
|
|
@@ -995,11 +1037,12 @@ function buildAndItemsPlan(childPlans) {
|
|
|
995
1037
|
unfoldablePlans.push(childPlan);
|
|
996
1038
|
continue;
|
|
997
1039
|
}
|
|
998
|
-
|
|
1040
|
+
appendUniquePredicates(mergedPlan.itemPredicates, childPlan.itemPredicates);
|
|
1041
|
+
appendUniquePredicates(mergedPlan.resultPredicates, childPlan.resultPredicates);
|
|
999
1042
|
mergedPlan.queryExpressions.push(...childPlan.queryExpressions);
|
|
1000
1043
|
}
|
|
1001
1044
|
if (unfoldablePlans.length === 0) return mergedPlan;
|
|
1002
|
-
const intersectedPlans = mergedPlan.itemPredicates.length === 0 && mergedPlan.queryExpressions.length === 0 ? unfoldablePlans : [mergedPlan, ...unfoldablePlans];
|
|
1045
|
+
const intersectedPlans = mergedPlan.itemPredicates.length === 0 && mergedPlan.queryExpressions.length === 0 && mergedPlan.resultPredicates.length === 0 ? unfoldablePlans : [mergedPlan, ...unfoldablePlans];
|
|
1003
1046
|
return intersectedPlans.length === 1 ? intersectedPlans[0] ?? mergedPlan : {
|
|
1004
1047
|
kind: "intersect",
|
|
1005
1048
|
children: intersectedPlans
|
|
@@ -1015,7 +1058,7 @@ function buildOrItemsPlan(childPlans) {
|
|
|
1015
1058
|
const mergedQueryExpressions = [];
|
|
1016
1059
|
const unionedPlans = [];
|
|
1017
1060
|
for (const childPlan of flattenItemsPlans(childPlans, "union")) {
|
|
1018
|
-
if (childPlan.kind === "search" && childPlan.itemPredicates.length === 0) {
|
|
1061
|
+
if (childPlan.kind === "search" && childPlan.itemPredicates.length === 0 && childPlan.resultPredicates.length === 0) {
|
|
1019
1062
|
mergedQueryExpressions.push(buildAndCtsQueryExpressionInternal(childPlan.queryExpressions));
|
|
1020
1063
|
continue;
|
|
1021
1064
|
}
|
|
@@ -1024,7 +1067,8 @@ function buildOrItemsPlan(childPlans) {
|
|
|
1024
1067
|
if (mergedQueryExpressions.length > 0) unionedPlans.unshift({
|
|
1025
1068
|
kind: "search",
|
|
1026
1069
|
itemPredicates: [],
|
|
1027
|
-
queryExpressions: [buildOrCtsQueryExpressionInternal(mergedQueryExpressions)]
|
|
1070
|
+
queryExpressions: [buildOrCtsQueryExpressionInternal(mergedQueryExpressions)],
|
|
1071
|
+
resultPredicates: []
|
|
1028
1072
|
});
|
|
1029
1073
|
if (unionedPlans.length === 0) return buildCtsItemsPlan("cts:false-query()");
|
|
1030
1074
|
return unionedPlans.length === 1 ? unionedPlans[0] ?? buildCtsItemsPlan("cts:false-query()") : {
|
|
@@ -1039,11 +1083,24 @@ function buildItemsPlan(context, query) {
|
|
|
1039
1083
|
return {
|
|
1040
1084
|
kind: "search",
|
|
1041
1085
|
itemPredicates: [query.isNegated === true ? `[not(@uuid = ${bindingName})]` : `[@uuid = ${bindingName}]`],
|
|
1042
|
-
queryExpressions: []
|
|
1086
|
+
queryExpressions: [],
|
|
1087
|
+
resultPredicates: []
|
|
1043
1088
|
};
|
|
1044
1089
|
}
|
|
1045
1090
|
const queryExpression = buildLeafQueryExpression(context, query);
|
|
1046
|
-
|
|
1091
|
+
if (query.isNegated === true) return buildNegatedItemsPlan(queryExpression);
|
|
1092
|
+
if (query.fallbackLanguage == null || query.fallbackLanguage === query.language) return buildCtsItemsPlan(queryExpression);
|
|
1093
|
+
return buildFallbackItemsPlan({
|
|
1094
|
+
supersetQueryExpression: buildOrCtsQueryExpressionInternal([buildLeafQueryExpression(context, {
|
|
1095
|
+
...query,
|
|
1096
|
+
fallbackLanguage: void 0
|
|
1097
|
+
}), buildLeafQueryExpression(context, {
|
|
1098
|
+
...query,
|
|
1099
|
+
language: query.fallbackLanguage,
|
|
1100
|
+
fallbackLanguage: void 0
|
|
1101
|
+
})]),
|
|
1102
|
+
strictQueryExpression: queryExpression
|
|
1103
|
+
});
|
|
1047
1104
|
}
|
|
1048
1105
|
const optimizedIncludesGroupQueries = getCompatibleIncludesGroupLeaves(query);
|
|
1049
1106
|
if (optimizedIncludesGroupQueries != null) return buildCtsItemsPlan(buildIncludesGroupQueryExpression(context, optimizedIncludesGroupQueries));
|
|
@@ -1062,7 +1119,8 @@ function buildItemsPlanExpression(parameters) {
|
|
|
1062
1119
|
if (plan.kind === "search") {
|
|
1063
1120
|
const itemsExpression = `${baseItemsExpression}${plan.itemPredicates.join("")}`;
|
|
1064
1121
|
const queryName = queryNamesByPlan.get(plan);
|
|
1065
|
-
|
|
1122
|
+
const searchedExpression = queryName == null ? itemsExpression : `cts:search(${itemsExpression}, ${queryName})`;
|
|
1123
|
+
return plan.resultPredicates.length === 0 ? searchedExpression : `(${searchedExpression})${plan.resultPredicates.join("")}`;
|
|
1066
1124
|
}
|
|
1067
1125
|
return `(${Array.from(plan.children, (childPlan) => buildItemsPlanExpression({
|
|
1068
1126
|
plan: childPlan,
|
|
@@ -1150,7 +1208,8 @@ function buildQueryPlan(parameters) {
|
|
|
1150
1208
|
const plan = queries == null ? {
|
|
1151
1209
|
kind: "search",
|
|
1152
1210
|
itemPredicates: [],
|
|
1153
|
-
queryExpressions: []
|
|
1211
|
+
queryExpressions: [],
|
|
1212
|
+
resultPredicates: []
|
|
1154
1213
|
} : buildItemsPlan(context, queries);
|
|
1155
1214
|
const searchPlans = [];
|
|
1156
1215
|
collectItemsSearchPlans(plan, searchPlans);
|
|
@@ -1202,11 +1261,12 @@ function buildQueryPlan(parameters) {
|
|
|
1202
1261
|
* @param parameters.queries - The query tree to compile, or null to match every item
|
|
1203
1262
|
* @param parameters.declarations - Extra prolog declarations, placed before the compiled prolog
|
|
1204
1263
|
* @param parameters.body - Builds the body from the name of the variable holding the items
|
|
1264
|
+
* @param parameters.contentLanguage - Keep only this language of every multilingual field in the result
|
|
1205
1265
|
* @returns A complete XQuery document
|
|
1206
1266
|
* @internal
|
|
1207
1267
|
*/
|
|
1208
1268
|
function compileContainerItemsQuery(parameters) {
|
|
1209
|
-
const { container, scopeUuids, belongsToCollectionScopeUuids, queries, declarations = [], body } = parameters;
|
|
1269
|
+
const { container, scopeUuids, belongsToCollectionScopeUuids, queries, declarations = [], body, contentLanguage } = parameters;
|
|
1210
1270
|
const { scopeVariable, itemsExpression } = ITEMS_CONTAINERS[container];
|
|
1211
1271
|
const plan = buildQueryPlan({
|
|
1212
1272
|
queries,
|
|
@@ -1225,7 +1285,8 @@ ${body({
|
|
|
1225
1285
|
...context,
|
|
1226
1286
|
items: plan.itemsVariable
|
|
1227
1287
|
})}
|
|
1228
|
-
}</ochre
|
|
1288
|
+
}</ochre>`,
|
|
1289
|
+
contentLanguage
|
|
1229
1290
|
});
|
|
1230
1291
|
}
|
|
1231
1292
|
/**
|
package/dist/schemas.mjs
CHANGED
|
@@ -84,6 +84,7 @@ const standardQueryFields = {
|
|
|
84
84
|
matchMode: v.picklist(["includes", "exact"]),
|
|
85
85
|
isCaseSensitive: v.boolean(),
|
|
86
86
|
language: defaultString("eng"),
|
|
87
|
+
fallbackLanguage: v.optional(v.string()),
|
|
87
88
|
isNegated: defaultBoolean(false)
|
|
88
89
|
};
|
|
89
90
|
const propertyRelationSchema = v.picklist(["related", "inverse"]);
|
package/dist/types/index.d.mts
CHANGED
|
@@ -853,6 +853,10 @@ export type SetItemsSort = {
|
|
|
853
853
|
* layer holds a single OCR word, so `includes` matches every search term as its
|
|
854
854
|
* own word and `exact` matches the terms as an adjacent run of whole words. OCR
|
|
855
855
|
* text carries no language, so `ocr` leaves take no `language`.
|
|
856
|
+
*
|
|
857
|
+
* `fallbackLanguage` searches a field in that language only when the field has
|
|
858
|
+
* no content in `language`, so a match follows the text a reader of `language`
|
|
859
|
+
* is shown. Leave it unset to search `language` alone.
|
|
856
860
|
*/
|
|
857
861
|
export type QueryLeaf = {
|
|
858
862
|
target: "property";
|
|
@@ -865,6 +869,7 @@ export type QueryLeaf = {
|
|
|
865
869
|
matchMode: "includes" | "exact";
|
|
866
870
|
isCaseSensitive: boolean;
|
|
867
871
|
language: string;
|
|
872
|
+
fallbackLanguage?: string;
|
|
868
873
|
isNegated?: boolean;
|
|
869
874
|
} | {
|
|
870
875
|
target: "property";
|
|
@@ -877,6 +882,7 @@ export type QueryLeaf = {
|
|
|
877
882
|
matchMode: "includes" | "exact";
|
|
878
883
|
isCaseSensitive: boolean;
|
|
879
884
|
language: string;
|
|
885
|
+
fallbackLanguage?: string;
|
|
880
886
|
isNegated?: boolean;
|
|
881
887
|
} | {
|
|
882
888
|
target: "property";
|
|
@@ -889,6 +895,7 @@ export type QueryLeaf = {
|
|
|
889
895
|
matchMode: "includes" | "exact";
|
|
890
896
|
isCaseSensitive: boolean;
|
|
891
897
|
language: string;
|
|
898
|
+
fallbackLanguage?: string;
|
|
892
899
|
isNegated?: boolean;
|
|
893
900
|
} | {
|
|
894
901
|
target: "property";
|
|
@@ -901,6 +908,7 @@ export type QueryLeaf = {
|
|
|
901
908
|
matchMode: "includes" | "exact";
|
|
902
909
|
isCaseSensitive: boolean;
|
|
903
910
|
language: string;
|
|
911
|
+
fallbackLanguage?: string;
|
|
904
912
|
isNegated?: boolean;
|
|
905
913
|
} | {
|
|
906
914
|
target: "property";
|
|
@@ -911,6 +919,7 @@ export type QueryLeaf = {
|
|
|
911
919
|
matchMode: "includes" | "exact";
|
|
912
920
|
isCaseSensitive: boolean;
|
|
913
921
|
language: string;
|
|
922
|
+
fallbackLanguage?: string;
|
|
914
923
|
isNegated?: boolean;
|
|
915
924
|
} | {
|
|
916
925
|
target: "string";
|
|
@@ -918,6 +927,7 @@ export type QueryLeaf = {
|
|
|
918
927
|
matchMode: "includes" | "exact";
|
|
919
928
|
isCaseSensitive: boolean;
|
|
920
929
|
language: string;
|
|
930
|
+
fallbackLanguage?: string;
|
|
921
931
|
isNegated?: boolean;
|
|
922
932
|
} | {
|
|
923
933
|
target: "ocr";
|
|
@@ -931,6 +941,7 @@ export type QueryLeaf = {
|
|
|
931
941
|
matchMode: "includes" | "exact";
|
|
932
942
|
isCaseSensitive: boolean;
|
|
933
943
|
language: string;
|
|
944
|
+
fallbackLanguage?: string;
|
|
934
945
|
isNegated?: boolean;
|
|
935
946
|
};
|
|
936
947
|
/**
|
package/dist/types/website.d.mts
CHANGED
|
@@ -87,6 +87,10 @@ export type WebLoadingVariant = "spinner" | "skeleton" | "animation" | "none";
|
|
|
87
87
|
* How an item page groups a repeated section
|
|
88
88
|
*/
|
|
89
89
|
export type WebSectionVariant = "discrete" | "tabs-horizontal" | "tabs-vertical" | "tabular";
|
|
90
|
+
/**
|
|
91
|
+
* How a website displays an item's related items
|
|
92
|
+
*/
|
|
93
|
+
export type WebRelationVariant = "list" | "dropdown-list" | "grid" | "gallery" | "inline";
|
|
90
94
|
/**
|
|
91
95
|
* The IIIF viewer a website renders images with
|
|
92
96
|
*/
|
|
@@ -206,6 +210,9 @@ export type Website<T extends LanguageCodes = LanguageCodes> = {
|
|
|
206
210
|
isPersistentIdentifierDisplayed: boolean;
|
|
207
211
|
iiifViewer: WebIiifViewer;
|
|
208
212
|
};
|
|
213
|
+
relations: {
|
|
214
|
+
variant: WebRelationVariant;
|
|
215
|
+
};
|
|
209
216
|
options: Prettify<WebOptions<T> & {
|
|
210
217
|
stylesheets: {
|
|
211
218
|
properties: Array<StylesheetItem>;
|
|
@@ -336,6 +343,11 @@ export type WebElementComponent<T extends LanguageCodes = LanguageCodes> = {
|
|
|
336
343
|
uuid: string;
|
|
337
344
|
label: MultilingualString<T> | null;
|
|
338
345
|
}> | null;
|
|
346
|
+
relations: Array<{
|
|
347
|
+
uuid: string;
|
|
348
|
+
label: MultilingualString<T> | null;
|
|
349
|
+
variant: WebRelationVariant | null;
|
|
350
|
+
}>;
|
|
339
351
|
variant: "slide" | "table" | "card" | "tile" | "showcase";
|
|
340
352
|
paginationVariant: "default" | "numeric";
|
|
341
353
|
loadingVariant: WebLoadingVariant;
|
package/dist/xquery.d.mts
CHANGED
|
@@ -17,6 +17,25 @@ export declare function stringLiteral(value: string): string;
|
|
|
17
17
|
* @internal
|
|
18
18
|
*/
|
|
19
19
|
export declare const OCHRE_RESPONSE_PADDING = "<!--ochre-sdk: keeps an empty result distinct from a failed query-->";
|
|
20
|
+
/**
|
|
21
|
+
* Which language a query should return, and what to show where it is missing
|
|
22
|
+
*/
|
|
23
|
+
export type OchreContentLanguage = {
|
|
24
|
+
language: string;
|
|
25
|
+
fallbackLanguage: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the content language a fetch asked for
|
|
29
|
+
* @param options - The fetch options
|
|
30
|
+
* @param options.language - The language to keep
|
|
31
|
+
* @param options.fallbackLanguage - The language shown where `language` is missing
|
|
32
|
+
* @returns The content language, or null to keep every language
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export declare function getContentLanguage(options?: {
|
|
36
|
+
language?: string;
|
|
37
|
+
fallbackLanguage?: string;
|
|
38
|
+
}): OchreContentLanguage | null;
|
|
20
39
|
/**
|
|
21
40
|
* What a query body can ask the surrounding document for
|
|
22
41
|
*
|
|
@@ -47,11 +66,13 @@ export type OchreQueryContext = {
|
|
|
47
66
|
* @param parameters - The document parameters
|
|
48
67
|
* @param parameters.declarations - Prolog declarations, emitted in order before the supplemental helper
|
|
49
68
|
* @param parameters.body - Builds the query body
|
|
69
|
+
* @param parameters.contentLanguage - Keep only this language of every multilingual field in the result
|
|
50
70
|
* @returns A complete XQuery document
|
|
51
71
|
* @internal
|
|
52
72
|
*/
|
|
53
73
|
export declare function compileOchreQuery(parameters: {
|
|
54
74
|
declarations?: ReadonlyArray<string>;
|
|
55
75
|
body: (context: OchreQueryContext) => string;
|
|
76
|
+
contentLanguage?: OchreContentLanguage | null;
|
|
56
77
|
}): string;
|
|
57
78
|
//#endregion
|
package/dist/xquery.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_LANGUAGES } from "./constants.mjs";
|
|
1
2
|
//#region src/xquery.ts
|
|
2
3
|
/**
|
|
3
4
|
* Build a string literal for an XQuery string
|
|
@@ -46,6 +47,63 @@ const SUPPLEMENTAL_XQUERY_PROLOG = `declare function local:omit-supplemental($no
|
|
|
46
47
|
}
|
|
47
48
|
else $node
|
|
48
49
|
};`;
|
|
50
|
+
/**
|
|
51
|
+
* XQuery prolog declaring `local:select-language`, which keeps one language of
|
|
52
|
+
* every multilingual field in a node sequence
|
|
53
|
+
*
|
|
54
|
+
* A field's `content` children are reduced to the one in `$language`, else the
|
|
55
|
+
* one in `$fallback`, else the first in any language, which is the choice the
|
|
56
|
+
* API's own `lang` parameter makes. `zxx` content is always kept, since it holds
|
|
57
|
+
* a field's aliases rather than a translation; the API's `lang` parameter drops
|
|
58
|
+
* it, which is why the SDK selects languages itself. Leaf elements are
|
|
59
|
+
* returned by reference; testing for multilingual descendants before copying a
|
|
60
|
+
* subtree measured slower than copying it.
|
|
61
|
+
*/
|
|
62
|
+
const LANGUAGE_XQUERY_PROLOG = `declare function local:select-language($nodes as node()*, $language as xs:string, $fallback as xs:string) as node()* {
|
|
63
|
+
for $node in $nodes
|
|
64
|
+
return
|
|
65
|
+
typeswitch ($node)
|
|
66
|
+
case element() return
|
|
67
|
+
if (empty($node/*))
|
|
68
|
+
then $node
|
|
69
|
+
else
|
|
70
|
+
let $contents := $node/content[@xml:lang]
|
|
71
|
+
let $selected :=
|
|
72
|
+
if (empty($contents))
|
|
73
|
+
then ()
|
|
74
|
+
else (
|
|
75
|
+
$contents[@xml:lang = $language],
|
|
76
|
+
$contents[@xml:lang = $fallback],
|
|
77
|
+
$contents[@xml:lang != "zxx"]
|
|
78
|
+
)[1]
|
|
79
|
+
return element { node-name($node) } {
|
|
80
|
+
$node/@*,
|
|
81
|
+
for $child in $node/node()
|
|
82
|
+
return
|
|
83
|
+
if ($child instance of element(content) and exists($child/@xml:lang))
|
|
84
|
+
then
|
|
85
|
+
if ($child is $selected or $child/@xml:lang = "zxx")
|
|
86
|
+
then $child
|
|
87
|
+
else ()
|
|
88
|
+
else local:select-language($child, $language, $fallback)
|
|
89
|
+
}
|
|
90
|
+
default return $node
|
|
91
|
+
};`;
|
|
92
|
+
/**
|
|
93
|
+
* Resolve the content language a fetch asked for
|
|
94
|
+
* @param options - The fetch options
|
|
95
|
+
* @param options.language - The language to keep
|
|
96
|
+
* @param options.fallbackLanguage - The language shown where `language` is missing
|
|
97
|
+
* @returns The content language, or null to keep every language
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
function getContentLanguage(options) {
|
|
101
|
+
if (options?.language == null) return null;
|
|
102
|
+
return {
|
|
103
|
+
language: options.language,
|
|
104
|
+
fallbackLanguage: options.fallbackLanguage ?? DEFAULT_LANGUAGES[0]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
49
107
|
const NOT_SUPPLEMENTAL_PREDICATE = "[not(ancestor-or-self::*[@supplemental = \"true\"])]";
|
|
50
108
|
/**
|
|
51
109
|
* Compile a complete XQuery document for the OCHRE API
|
|
@@ -57,21 +115,27 @@ const NOT_SUPPLEMENTAL_PREDICATE = "[not(ancestor-or-self::*[@supplemental = \"t
|
|
|
57
115
|
* @param parameters - The document parameters
|
|
58
116
|
* @param parameters.declarations - Prolog declarations, emitted in order before the supplemental helper
|
|
59
117
|
* @param parameters.body - Builds the query body
|
|
118
|
+
* @param parameters.contentLanguage - Keep only this language of every multilingual field in the result
|
|
60
119
|
* @returns A complete XQuery document
|
|
61
120
|
* @internal
|
|
62
121
|
*/
|
|
63
122
|
function compileOchreQuery(parameters) {
|
|
64
|
-
const { declarations = [], body } = parameters;
|
|
65
|
-
|
|
123
|
+
const { declarations = [], body, contentLanguage } = parameters;
|
|
124
|
+
const prologDeclarations = [
|
|
66
125
|
"xquery version \"1.0-ml\";",
|
|
67
126
|
...declarations,
|
|
68
|
-
SUPPLEMENTAL_XQUERY_PROLOG
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
127
|
+
SUPPLEMENTAL_XQUERY_PROLOG,
|
|
128
|
+
...contentLanguage == null ? [] : [LANGUAGE_XQUERY_PROLOG]
|
|
129
|
+
];
|
|
130
|
+
const bodyExpression = body({
|
|
72
131
|
omitSupplemental: (expression) => `local:omit-supplemental(${expression})`,
|
|
73
132
|
notSupplemental: NOT_SUPPLEMENTAL_PREDICATE
|
|
74
|
-
})
|
|
133
|
+
});
|
|
134
|
+
return `${prologDeclarations.join("\n\n")}
|
|
135
|
+
|
|
136
|
+
${contentLanguage == null ? bodyExpression : `local:select-language((
|
|
137
|
+
${bodyExpression}
|
|
138
|
+
), ${stringLiteral(contentLanguage.language)}, ${stringLiteral(contentLanguage.fallbackLanguage)})`}`;
|
|
75
139
|
}
|
|
76
140
|
//#endregion
|
|
77
|
-
export { OCHRE_RESPONSE_PADDING, compileOchreQuery, stringLiteral };
|
|
141
|
+
export { OCHRE_RESPONSE_PADDING, compileOchreQuery, getContentLanguage, stringLiteral };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ochre-sdk",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Node.js library for working with OCHRE (Online Cultural and Historical Research Environment) data",
|
|
@@ -45,20 +45,20 @@
|
|
|
45
45
|
"dist"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"fast-equals": "^6.0
|
|
48
|
+
"fast-equals": "^6.1.0",
|
|
49
49
|
"fast-xml-parser": "^5.11.2",
|
|
50
50
|
"valibot": "^1.5.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@antfu/eslint-config": "^9.5.1",
|
|
54
|
-
"@types/node": "^24.19.
|
|
54
|
+
"@types/node": "^24.19.1",
|
|
55
55
|
"bumpp": "^12.3.0",
|
|
56
|
-
"eslint": "^10.
|
|
56
|
+
"eslint": "^10.12.0",
|
|
57
57
|
"eslint-plugin-erasable-syntax-only": "^0.7.2",
|
|
58
58
|
"eslint-plugin-slop": "^0.1.3",
|
|
59
59
|
"eslint-plugin-sonarjs": "^4.2.2",
|
|
60
60
|
"knip": "^6.39.0",
|
|
61
|
-
"oxfmt": "^0.
|
|
61
|
+
"oxfmt": "^0.72.0",
|
|
62
62
|
"tsdown": "^0.23.0",
|
|
63
63
|
"typescript": "^6.0.3",
|
|
64
64
|
"vitest": "^5.0.3"
|