ochre-sdk 1.0.78 → 1.1.0
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/README.md +31 -3
- package/dist/_virtual/_rolldown/runtime.mjs +13 -0
- package/dist/categories.d.mts +249 -0
- package/dist/categories.mjs +259 -0
- package/dist/constants.d.mts +14 -0
- package/dist/constants.mjs +18 -1
- package/dist/errors.d.mts +23 -0
- package/dist/{utilities.mjs → errors.mjs} +33 -84
- package/dist/fetchers/gallery.mjs +15 -51
- package/dist/fetchers/item-children.mjs +20 -66
- package/dist/fetchers/item-links.mjs +26 -79
- package/dist/fetchers/item-ocr-data.d.mts +2 -2
- package/dist/fetchers/item-ocr-data.mjs +11 -15
- package/dist/fetchers/item.d.mts +0 -15
- package/dist/fetchers/item.mjs +20 -72
- package/dist/fetchers/request.d.mts +70 -0
- package/dist/fetchers/request.mjs +100 -0
- package/dist/fetchers/set/items.mjs +27 -73
- package/dist/fetchers/set/property-values.d.mts +2 -3
- package/dist/fetchers/set/property-values.mjs +96 -130
- package/dist/fetchers/website-metadata.mjs +35 -57
- package/dist/fetchers/website.d.mts +2 -3
- package/dist/fetchers/website.mjs +22 -31
- package/dist/getters.d.mts +78 -148
- package/dist/getters.mjs +127 -208
- package/dist/helpers.d.mts +0 -4
- package/dist/helpers.mjs +19 -6
- package/dist/index.d.mts +8 -6
- package/dist/index.mjs +6 -4
- package/dist/ocr.d.mts +37 -0
- package/dist/ocr.mjs +52 -0
- package/dist/parsers/helpers.d.mts +21 -1
- package/dist/parsers/helpers.mjs +26 -6
- package/dist/parsers/index.d.mts +0 -8
- package/dist/parsers/index.mjs +118 -259
- package/dist/parsers/languages.d.mts +72 -0
- package/dist/parsers/languages.mjs +132 -0
- package/dist/parsers/multilingual.d.mts +49 -74
- package/dist/parsers/multilingual.mjs +88 -189
- package/dist/parsers/property-token.d.mts +34 -0
- package/dist/parsers/property-token.mjs +29 -0
- package/dist/parsers/string.d.mts +19 -0
- package/dist/parsers/string.mjs +45 -25
- package/dist/parsers/website/bounds.d.mts +10 -0
- package/dist/parsers/website/bounds.mjs +28 -0
- package/dist/parsers/website/components.d.mts +91 -0
- package/dist/parsers/website/components.mjs +681 -0
- package/dist/parsers/website/index.d.mts +0 -7
- package/dist/parsers/website/index.mjs +92 -1153
- package/dist/parsers/website/links.d.mts +36 -0
- package/dist/parsers/website/links.mjs +58 -0
- package/dist/parsers/website/messages.d.mts +24 -0
- package/dist/parsers/website/messages.mjs +31 -0
- package/dist/parsers/website/options.d.mts +6 -0
- package/dist/parsers/website/options.mjs +114 -0
- package/dist/parsers/website/properties.d.mts +12 -0
- package/dist/parsers/website/properties.mjs +158 -0
- package/dist/parsers/website/reader.d.mts +54 -4
- package/dist/parsers/website/reader.mjs +65 -20
- package/dist/parsers/website/slug.d.mts +64 -0
- package/dist/parsers/website/slug.mjs +82 -0
- package/dist/parsers/website/styles.d.mts +28 -0
- package/dist/parsers/website/styles.mjs +103 -0
- package/dist/parsers/website/walk.d.mts +68 -0
- package/dist/parsers/website/walk.mjs +116 -0
- package/dist/query.d.mts +66 -18
- package/dist/query.mjs +202 -48
- package/dist/reflection.d.mts +64 -0
- package/dist/reflection.mjs +79 -0
- package/dist/schemas.d.mts +7 -0
- package/dist/schemas.mjs +12 -3
- package/dist/types/index.d.mts +1 -31
- package/dist/types/utilities.d.mts +9 -0
- package/dist/types/utilities.mjs +1 -0
- package/dist/types/website.d.mts +49 -58
- package/dist/xml/metadata.d.mts +16 -0
- package/dist/xml/metadata.mjs +32 -11
- package/dist/xml/schemas.d.mts +5970 -3
- package/dist/xml/schemas.mjs +43 -45
- package/dist/xml/types.d.mts +13 -30
- package/dist/xquery.d.mts +46 -0
- package/dist/xquery.mjs +66 -0
- package/package.json +3 -3
- package/dist/utilities.d.mts +0 -54
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/parsers/website/slug.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* How a website page slug is built
|
|
4
|
+
*
|
|
5
|
+
* The rule runs in two places: here, while parsing a website, and inside the
|
|
6
|
+
* website metadata fetcher's XQuery, which resolves a slug to a page without
|
|
7
|
+
* shipping the whole tree. Both adapters live in this module so the rule
|
|
8
|
+
* cannot drift. The only difference between them is how "no prefix" is
|
|
9
|
+
* spelled: `undefined` in TypeScript, the empty string in XQuery, which has no
|
|
10
|
+
* way to pass an absent argument.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The prefix OCHRE puts on a segment page's slug to keep it unique
|
|
14
|
+
*/
|
|
15
|
+
export declare const SEGMENT_UNIQUE_SLUG_PREFIX_PATTERN: string;
|
|
16
|
+
/**
|
|
17
|
+
* The separator between a slug and its prefix
|
|
18
|
+
*/
|
|
19
|
+
export declare const WEBSITE_PAGE_SLUG_SEPARATOR = "/";
|
|
20
|
+
/**
|
|
21
|
+
* The full slug of every page in a website, keyed by the page's UUID
|
|
22
|
+
*
|
|
23
|
+
* Resolved before parsing begins, because a link on any page can point at any
|
|
24
|
+
* other page and the target's slug depends on where that target sits in the
|
|
25
|
+
* tree.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export type WebsitePageSlugs = ReadonlyMap<string, string>;
|
|
29
|
+
/**
|
|
30
|
+
* Strip the uniqueness prefix from a website page slug
|
|
31
|
+
* @param slug - The raw slug
|
|
32
|
+
* @returns The cleaned slug, or null when there was none
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export declare function cleanWebsitePageSlug(slug: string | undefined): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Join a website page slug onto the slug of the segment holding it
|
|
38
|
+
* @param slug - The page slug
|
|
39
|
+
* @param slugPrefix - The slug of the enclosing segment, if any
|
|
40
|
+
* @returns The full slug
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
export declare function prefixSlug(slug: string, slugPrefix: string | undefined): string;
|
|
44
|
+
/**
|
|
45
|
+
* The slug a page gives its own child pages
|
|
46
|
+
*
|
|
47
|
+
* A page only prefixes its children once it is itself inside a segment, so a
|
|
48
|
+
* top-level website keeps flat page slugs.
|
|
49
|
+
* @param pageSlug - The page's own full slug
|
|
50
|
+
* @param slugPrefix - The prefix the page was resolved with
|
|
51
|
+
* @returns The prefix for the page's children, or undefined for no prefix
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
export declare function childSlugPrefix(pageSlug: string, slugPrefix: string | undefined): string | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* The XQuery counterpart of {@link cleanWebsitePageSlug},
|
|
57
|
+
* {@link prefixSlug} and {@link childSlugPrefix}
|
|
58
|
+
*
|
|
59
|
+
* Declares `local:clean-slug`, `local:page-slug` and
|
|
60
|
+
* `local:page-child-slug-prefix`. "No prefix" is the empty string here.
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
export declare const WEBSITE_PAGE_SLUG_DECLARATIONS: string;
|
|
64
|
+
//#endregion
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { stringLiteral } from "../../xquery.mjs";
|
|
2
|
+
//#region src/parsers/website/slug.ts
|
|
3
|
+
/**
|
|
4
|
+
* How a website page slug is built
|
|
5
|
+
*
|
|
6
|
+
* The rule runs in two places: here, while parsing a website, and inside the
|
|
7
|
+
* website metadata fetcher's XQuery, which resolves a slug to a page without
|
|
8
|
+
* shipping the whole tree. Both adapters live in this module so the rule
|
|
9
|
+
* cannot drift. The only difference between them is how "no prefix" is
|
|
10
|
+
* spelled: `undefined` in TypeScript, the empty string in XQuery, which has no
|
|
11
|
+
* way to pass an absent argument.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The prefix OCHRE puts on a segment page's slug to keep it unique
|
|
15
|
+
*/
|
|
16
|
+
const SEGMENT_UNIQUE_SLUG_PREFIX_PATTERN = String.raw`^\$[^-]*-`;
|
|
17
|
+
/**
|
|
18
|
+
* The separator between a slug and its prefix
|
|
19
|
+
*/
|
|
20
|
+
const WEBSITE_PAGE_SLUG_SEPARATOR = "/";
|
|
21
|
+
const SEGMENT_UNIQUE_SLUG_PREFIX_REGEX = new RegExp(SEGMENT_UNIQUE_SLUG_PREFIX_PATTERN);
|
|
22
|
+
/**
|
|
23
|
+
* Strip the uniqueness prefix from a website page slug
|
|
24
|
+
* @param slug - The raw slug
|
|
25
|
+
* @returns The cleaned slug, or null when there was none
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
function cleanWebsitePageSlug(slug) {
|
|
29
|
+
return slug?.replace(SEGMENT_UNIQUE_SLUG_PREFIX_REGEX, "") ?? null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Join a website page slug onto the slug of the segment holding it
|
|
33
|
+
* @param slug - The page slug
|
|
34
|
+
* @param slugPrefix - The slug of the enclosing segment, if any
|
|
35
|
+
* @returns The full slug
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
function prefixSlug(slug, slugPrefix) {
|
|
39
|
+
if (slugPrefix === "" || slugPrefix == null) return slug;
|
|
40
|
+
if (slug === "") return slugPrefix;
|
|
41
|
+
return `${slugPrefix}/${slug}`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The slug a page gives its own child pages
|
|
45
|
+
*
|
|
46
|
+
* A page only prefixes its children once it is itself inside a segment, so a
|
|
47
|
+
* top-level website keeps flat page slugs.
|
|
48
|
+
* @param pageSlug - The page's own full slug
|
|
49
|
+
* @param slugPrefix - The prefix the page was resolved with
|
|
50
|
+
* @returns The prefix for the page's children, or undefined for no prefix
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
function childSlugPrefix(pageSlug, slugPrefix) {
|
|
54
|
+
return slugPrefix == null ? void 0 : pageSlug;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The XQuery counterpart of {@link cleanWebsitePageSlug},
|
|
58
|
+
* {@link prefixSlug} and {@link childSlugPrefix}
|
|
59
|
+
*
|
|
60
|
+
* Declares `local:clean-slug`, `local:page-slug` and
|
|
61
|
+
* `local:page-child-slug-prefix`. "No prefix" is the empty string here.
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
const WEBSITE_PAGE_SLUG_DECLARATIONS = `declare function local:clean-slug($slug) {
|
|
65
|
+
replace(string($slug), ${stringLiteral(SEGMENT_UNIQUE_SLUG_PREFIX_PATTERN)}, "")
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
declare function local:page-slug($resource, $slug-prefix) {
|
|
69
|
+
let $slug := local:clean-slug($resource/@slug)
|
|
70
|
+
return
|
|
71
|
+
if ($slug-prefix = "") then $slug
|
|
72
|
+
else if ($slug = "") then $slug-prefix
|
|
73
|
+
else concat($slug-prefix, ${stringLiteral("/")}, $slug)
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
declare function local:page-child-slug-prefix($resource, $slug-prefix) {
|
|
77
|
+
if ($slug-prefix = "") then ""
|
|
78
|
+
else local:page-slug($resource, $slug-prefix)
|
|
79
|
+
};
|
|
80
|
+
`;
|
|
81
|
+
//#endregion
|
|
82
|
+
export { SEGMENT_UNIQUE_SLUG_PREFIX_PATTERN, WEBSITE_PAGE_SLUG_DECLARATIONS, WEBSITE_PAGE_SLUG_SEPARATOR, childSlugPrefix, cleanWebsitePageSlug, prefixSlug };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ResponsiveStyles, StylesheetItem } from "../../types/website.mjs";
|
|
2
|
+
import { SimplifiedProperty } from "../../types/index.mjs";
|
|
3
|
+
import { XMLWebsiteStyle } from "../../xml/types.mjs";
|
|
4
|
+
//#region src/parsers/website/styles.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* The CSS declarations for every viewport a resource defines
|
|
7
|
+
* @param properties - Array of properties to parse
|
|
8
|
+
* @returns The styles per viewport
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseResponsiveCssStyles<T extends ReadonlyArray<string>>(properties: ReadonlyArray<SimplifiedProperty<T>>): ResponsiveStyles;
|
|
12
|
+
/**
|
|
13
|
+
* An empty set of responsive styles, for resources that define none
|
|
14
|
+
* @returns The empty styles
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export declare function emptyResponsiveStyles(): ResponsiveStyles;
|
|
18
|
+
/**
|
|
19
|
+
* The stylesheet a website attaches to a property variable or value
|
|
20
|
+
*
|
|
21
|
+
* OCHRE has no viewport dimension on stylesheet entries, so these only ever
|
|
22
|
+
* carry default styles, unlike the per-element styles above.
|
|
23
|
+
* @param styles - The raw stylesheet entries
|
|
24
|
+
* @returns The parsed stylesheet items
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseStylesheets(styles: Array<XMLWebsiteStyle>): Array<StylesheetItem>;
|
|
28
|
+
//#endregion
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { getProperty } from "../../getters.mjs";
|
|
2
|
+
//#region src/parsers/website/styles.ts
|
|
3
|
+
const STYLESHEET_NON_STYLE_KEYS = /* @__PURE__ */ new Set([
|
|
4
|
+
"variableUuid",
|
|
5
|
+
"valueUuid",
|
|
6
|
+
"category",
|
|
7
|
+
"payload",
|
|
8
|
+
"content"
|
|
9
|
+
]);
|
|
10
|
+
/**
|
|
11
|
+
* The CSS declarations a presentation variant carries
|
|
12
|
+
*
|
|
13
|
+
* @param properties - Array of properties to parse
|
|
14
|
+
* @param cssVariant - CSS variant to parse
|
|
15
|
+
* @returns Array of CSS styles
|
|
16
|
+
*/
|
|
17
|
+
function parseCssStylesFromProperties(properties, cssVariant) {
|
|
18
|
+
const cssProperties = getProperty(properties, {
|
|
19
|
+
label: "presentation",
|
|
20
|
+
valueContent: cssVariant != null ? `css-${cssVariant}` : "css"
|
|
21
|
+
})?.properties ?? [];
|
|
22
|
+
const styles = [];
|
|
23
|
+
for (const property of cssProperties) {
|
|
24
|
+
const value = property.values[0]?.content.toString();
|
|
25
|
+
if (value != null) styles.push({
|
|
26
|
+
label: property.variable.label,
|
|
27
|
+
value
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return styles;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The CSS declarations for every viewport a resource defines
|
|
34
|
+
* @param properties - Array of properties to parse
|
|
35
|
+
* @returns The styles per viewport
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
function parseResponsiveCssStyles(properties) {
|
|
39
|
+
return {
|
|
40
|
+
default: parseCssStylesFromProperties(properties),
|
|
41
|
+
tablet: parseCssStylesFromProperties(properties, "tablet"),
|
|
42
|
+
mobile: parseCssStylesFromProperties(properties, "mobile")
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* An empty set of responsive styles, for resources that define none
|
|
47
|
+
* @returns The empty styles
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
function emptyResponsiveStyles() {
|
|
51
|
+
return {
|
|
52
|
+
default: [],
|
|
53
|
+
tablet: [],
|
|
54
|
+
mobile: []
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The stylesheet a website attaches to a property variable or value
|
|
59
|
+
*
|
|
60
|
+
* OCHRE has no viewport dimension on stylesheet entries, so these only ever
|
|
61
|
+
* carry default styles, unlike the per-element styles above.
|
|
62
|
+
* @param styles - The raw stylesheet entries
|
|
63
|
+
* @returns The parsed stylesheet items
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
function parseStylesheets(styles) {
|
|
67
|
+
const parsedStyles = [];
|
|
68
|
+
for (const style of styles) {
|
|
69
|
+
const defaultStyles = [];
|
|
70
|
+
for (const [label, value] of Object.entries(style)) {
|
|
71
|
+
if (STYLESHEET_NON_STYLE_KEYS.has(label)) continue;
|
|
72
|
+
const valueString = value?.toString();
|
|
73
|
+
if (valueString != null) defaultStyles.push({
|
|
74
|
+
label,
|
|
75
|
+
value: valueString
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const stylesByViewport = {
|
|
79
|
+
...emptyResponsiveStyles(),
|
|
80
|
+
default: defaultStyles
|
|
81
|
+
};
|
|
82
|
+
if (style.category === "propertyValue" || style.valueUuid != null) {
|
|
83
|
+
if (style.valueUuid == null) throw new Error(`Stylesheet property value "${style.variableUuid}" is missing a value UUID`, { cause: style });
|
|
84
|
+
parsedStyles.push({
|
|
85
|
+
uuid: style.valueUuid,
|
|
86
|
+
category: "propertyValue",
|
|
87
|
+
variableUuid: style.variableUuid,
|
|
88
|
+
icon: style.lucideIcon ?? null,
|
|
89
|
+
styles: stylesByViewport
|
|
90
|
+
});
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
parsedStyles.push({
|
|
94
|
+
uuid: style.variableUuid,
|
|
95
|
+
category: "propertyVariable",
|
|
96
|
+
icon: style.lucideIcon ?? null,
|
|
97
|
+
styles: stylesByViewport
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return parsedStyles;
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
export { emptyResponsiveStyles, parseResponsiveCssStyles, parseStylesheets };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { XMLWebsiteResource, XMLWebsiteResourceItem, XMLWebsiteTree } from "../../xml/types.mjs";
|
|
2
|
+
import { ParserOptions } from "../helpers.mjs";
|
|
3
|
+
import { WebsitePageSlugs } from "./slug.mjs";
|
|
4
|
+
//#region src/parsers/website/walk.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* A page in a website, with its slug already resolved
|
|
7
|
+
*
|
|
8
|
+
* OCHRE stores a page's slug relative to the segment holding it, so the full
|
|
9
|
+
* slug depends on the path taken to reach the page. That path is walked once,
|
|
10
|
+
* here, and both the page parser and the slug map read the result instead of
|
|
11
|
+
* each re-deriving it.
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export type WebsitePageNode = {
|
|
15
|
+
resource: XMLWebsiteResource;
|
|
16
|
+
slug: string;
|
|
17
|
+
children: Array<WebsitePageNode>;
|
|
18
|
+
segments: Array<{
|
|
19
|
+
tree: XMLWebsiteTree;
|
|
20
|
+
slugPrefix: string;
|
|
21
|
+
}>;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Flatten the wrapper elements OCHRE nests resources in
|
|
25
|
+
*
|
|
26
|
+
* A `<resource>` holding only more resources is grouping, not content. Note
|
|
27
|
+
* that this drops segment wrappers, which carry neither an identification nor
|
|
28
|
+
* a nested resource list; {@link readWebsitePages} picks those up separately,
|
|
29
|
+
* because a segment becomes its own website rather than a resource.
|
|
30
|
+
* @param resources - The raw resource items
|
|
31
|
+
* @returns The identified resources, in source order
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
export declare function normalizeWebsiteResources(resources: Array<XMLWebsiteResourceItem> | undefined): Array<XMLWebsiteResource>;
|
|
35
|
+
/**
|
|
36
|
+
* The pages directly under a set of resources, and everything under them
|
|
37
|
+
*
|
|
38
|
+
* Only pages carry routes, so this descends through pages and the segments
|
|
39
|
+
* they hold and stops at anything else. Blocks and elements are the page's
|
|
40
|
+
* content and are parsed by the page, not walked for further pages.
|
|
41
|
+
* @param resources - The raw resource items to read
|
|
42
|
+
* @param options - Parser options
|
|
43
|
+
* @param slugPrefix - The slug of the enclosing segment, if any
|
|
44
|
+
* @returns The page nodes, in source order
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export declare function readWebsitePages<T extends ReadonlyArray<string>>(resources: Array<XMLWebsiteResourceItem> | undefined, options: ParserOptions<T>, slugPrefix?: string): Array<WebsitePageNode>;
|
|
48
|
+
/**
|
|
49
|
+
* Every page's full slug, keyed by UUID, including pages inside segments
|
|
50
|
+
* @param pages - The page nodes to collect from
|
|
51
|
+
* @param options - Parser options
|
|
52
|
+
* @param pageSlugsByUuid - The map to fill, for the recursive calls
|
|
53
|
+
* @returns The filled map
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
export declare function collectWebsitePageSlugs<T extends ReadonlyArray<string>>(pages: ReadonlyArray<WebsitePageNode>, options: ParserOptions<T>, pageSlugsByUuid?: Map<string, string>): WebsitePageSlugs;
|
|
57
|
+
/**
|
|
58
|
+
* The XQuery counterpart of {@link normalizeWebsiteResources} and the page
|
|
59
|
+
* descent in {@link readWebsitePages}
|
|
60
|
+
*
|
|
61
|
+
* Declares `local:resource-items`, `local:presentation` and, from
|
|
62
|
+
* {@link WEBSITE_PAGE_SLUG_DECLARATIONS}, the slug rules. Segment wrappers are
|
|
63
|
+
* kept here rather than dropped, because the caller filters for pages by
|
|
64
|
+
* presentation and reads segments off the same sequence.
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
export declare const WEBSITE_WALK_DECLARATIONS: string;
|
|
68
|
+
//#endregion
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { parseStringContent } from "../helpers.mjs";
|
|
2
|
+
import { parseSimplifiedProperties } from "../index.mjs";
|
|
3
|
+
import { stringLiteral } from "../../xquery.mjs";
|
|
4
|
+
import { formatXMLWebsiteResourceMetadata } from "./messages.mjs";
|
|
5
|
+
import { websitePresentationReader } from "./reader.mjs";
|
|
6
|
+
import { WEBSITE_PAGE_SLUG_DECLARATIONS, childSlugPrefix, cleanWebsitePageSlug, prefixSlug } from "./slug.mjs";
|
|
7
|
+
//#region src/parsers/website/walk.ts
|
|
8
|
+
/**
|
|
9
|
+
* Flatten the wrapper elements OCHRE nests resources in
|
|
10
|
+
*
|
|
11
|
+
* A `<resource>` holding only more resources is grouping, not content. Note
|
|
12
|
+
* that this drops segment wrappers, which carry neither an identification nor
|
|
13
|
+
* a nested resource list; {@link readWebsitePages} picks those up separately,
|
|
14
|
+
* because a segment becomes its own website rather than a resource.
|
|
15
|
+
* @param resources - The raw resource items
|
|
16
|
+
* @returns The identified resources, in source order
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
function normalizeWebsiteResources(resources) {
|
|
20
|
+
const normalized = [];
|
|
21
|
+
const resourcesToNormalize = resources ?? [];
|
|
22
|
+
for (const resource of resourcesToNormalize) {
|
|
23
|
+
if ("identification" in resource) {
|
|
24
|
+
normalized.push(resource);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if ("resource" in resource) normalized.push(...resource.resource);
|
|
28
|
+
}
|
|
29
|
+
return normalized;
|
|
30
|
+
}
|
|
31
|
+
function readWebsiteSegments(resources, options, slugPrefix) {
|
|
32
|
+
const segments = [];
|
|
33
|
+
const segmentResources = resources ?? [];
|
|
34
|
+
for (const resource of segmentResources) {
|
|
35
|
+
if (!("segments" in resource)) continue;
|
|
36
|
+
for (const tree of resource.segments.tree) {
|
|
37
|
+
const segmentSlug = tree.identification.abbreviation == null ? null : parseStringContent(tree.identification.abbreviation, options);
|
|
38
|
+
if (segmentSlug == null) throw new Error(`Slug not found for segment website (website uuid “${tree.uuid}”)`, { cause: tree });
|
|
39
|
+
segments.push({
|
|
40
|
+
tree,
|
|
41
|
+
slugPrefix: prefixSlug(segmentSlug, slugPrefix)
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return segments;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The pages directly under a set of resources, and everything under them
|
|
49
|
+
*
|
|
50
|
+
* Only pages carry routes, so this descends through pages and the segments
|
|
51
|
+
* they hold and stops at anything else. Blocks and elements are the page's
|
|
52
|
+
* content and are parsed by the page, not walked for further pages.
|
|
53
|
+
* @param resources - The raw resource items to read
|
|
54
|
+
* @param options - Parser options
|
|
55
|
+
* @param slugPrefix - The slug of the enclosing segment, if any
|
|
56
|
+
* @returns The page nodes, in source order
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
function readWebsitePages(resources, options, slugPrefix) {
|
|
60
|
+
const pages = [];
|
|
61
|
+
for (const resource of normalizeWebsiteResources(resources)) {
|
|
62
|
+
const properties = parseSimplifiedProperties(resource.properties, options);
|
|
63
|
+
if (websitePresentationReader(properties).value("presentation") !== "page") continue;
|
|
64
|
+
const slug = cleanWebsitePageSlug(resource.slug);
|
|
65
|
+
if (slug == null) throw new Error(`Slug not found for page (${formatXMLWebsiteResourceMetadata(resource)})`, { cause: resource });
|
|
66
|
+
const pageSlug = prefixSlug(slug, slugPrefix);
|
|
67
|
+
pages.push({
|
|
68
|
+
resource,
|
|
69
|
+
slug: pageSlug,
|
|
70
|
+
children: readWebsitePages(resource.resource, options, childSlugPrefix(pageSlug, slugPrefix)),
|
|
71
|
+
segments: readWebsiteSegments(resource.resource, options, pageSlug)
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return pages;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Every page's full slug, keyed by UUID, including pages inside segments
|
|
78
|
+
* @param pages - The page nodes to collect from
|
|
79
|
+
* @param options - Parser options
|
|
80
|
+
* @param pageSlugsByUuid - The map to fill, for the recursive calls
|
|
81
|
+
* @returns The filled map
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
function collectWebsitePageSlugs(pages, options, pageSlugsByUuid = /* @__PURE__ */ new Map()) {
|
|
85
|
+
for (const page of pages) {
|
|
86
|
+
pageSlugsByUuid.set(page.resource.uuid, page.slug);
|
|
87
|
+
collectWebsitePageSlugs(page.children, options, pageSlugsByUuid);
|
|
88
|
+
for (const segment of page.segments) collectWebsitePageSlugs(readWebsitePages(segment.tree.items?.resource, options, segment.slugPrefix), options, pageSlugsByUuid);
|
|
89
|
+
}
|
|
90
|
+
return pageSlugsByUuid;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The XQuery counterpart of {@link normalizeWebsiteResources} and the page
|
|
94
|
+
* descent in {@link readWebsitePages}
|
|
95
|
+
*
|
|
96
|
+
* Declares `local:resource-items`, `local:presentation` and, from
|
|
97
|
+
* {@link WEBSITE_PAGE_SLUG_DECLARATIONS}, the slug rules. Segment wrappers are
|
|
98
|
+
* kept here rather than dropped, because the caller filters for pages by
|
|
99
|
+
* presentation and reads segments off the same sequence.
|
|
100
|
+
* @internal
|
|
101
|
+
*/
|
|
102
|
+
const WEBSITE_WALK_DECLARATIONS = `declare function local:resource-items($resources) {
|
|
103
|
+
for $resource in $resources
|
|
104
|
+
return
|
|
105
|
+
if ($resource/segments) then $resource
|
|
106
|
+
else if ($resource/identification) then $resource
|
|
107
|
+
else local:resource-items($resource/resource)
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
declare function local:presentation($resource) {
|
|
111
|
+
string(($resource/properties/property[label/string() = ${stringLiteral("presentation")}]/value)[1])
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
${WEBSITE_PAGE_SLUG_DECLARATIONS}`;
|
|
115
|
+
//#endregion
|
|
116
|
+
export { WEBSITE_WALK_DECLARATIONS, collectWebsitePageSlugs, normalizeWebsiteResources, readWebsitePages };
|
package/dist/query.d.mts
CHANGED
|
@@ -1,24 +1,20 @@
|
|
|
1
|
-
import { Query } from "./types/index.mjs";
|
|
1
|
+
import { PropertyRelation, Query } from "./types/index.mjs";
|
|
2
|
+
import { OchreQueryContext } from "./xquery.mjs";
|
|
2
3
|
//#region src/query.d.ts
|
|
3
|
-
export declare function buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids:
|
|
4
|
+
export declare function buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids: ReadonlyArray<string>, belongsToCollectionPropertyVariableUuid: string): string | null;
|
|
4
5
|
/**
|
|
5
|
-
* Compile a query tree into the
|
|
6
|
-
* matching Set items
|
|
6
|
+
* Compile a query tree into the clauses that bind the matching Set items
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* @
|
|
18
|
-
* @param parameters.queries - Recursive query tree to compile, if any
|
|
19
|
-
* @param parameters.baseItemsExpression - The inline XQuery path selecting the items to search
|
|
20
|
-
* @param parameters.scopeQueryExpression - An optional CTS query ANDed into every compiled search
|
|
21
|
-
* @returns The prolog declaring the query helpers, and the `let` clauses binding `$items`
|
|
8
|
+
* The returned `itemsClause` binds {@link ITEMS_VARIABLE} and has to be placed
|
|
9
|
+
* inside an XQuery body, with `prolog` declared ahead of it.
|
|
10
|
+
* {@link compileSetItemsQuery} does both and is what fetchers should use;
|
|
11
|
+
* this is exposed for tests that assert on the compiled CTS.
|
|
12
|
+
* @param parameters - The plan parameters
|
|
13
|
+
* @param parameters.queries - The query tree to compile, or null to match every item
|
|
14
|
+
* @param parameters.baseItemsExpression - The inline searchable path to filter
|
|
15
|
+
* @param parameters.scopeQueryExpression - An extra query AND-ed into every search
|
|
16
|
+
* @returns The prolog, the clauses binding the items, and the bound CTS queries
|
|
17
|
+
* @internal
|
|
22
18
|
*/
|
|
23
19
|
export declare function buildQueryPlan(parameters: {
|
|
24
20
|
queries: Query | null;
|
|
@@ -27,5 +23,57 @@ export declare function buildQueryPlan(parameters: {
|
|
|
27
23
|
}): {
|
|
28
24
|
prolog: string;
|
|
29
25
|
itemsClause: string;
|
|
26
|
+
itemsVariable: string;
|
|
27
|
+
queryBindings: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
expression: string;
|
|
30
|
+
}>;
|
|
30
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* Compile a Set item query into a complete XQuery document
|
|
34
|
+
*
|
|
35
|
+
* Owns everything a caller would otherwise have to know and restate: the
|
|
36
|
+
* version declaration, the Set scope variable, the supplemental-stripping
|
|
37
|
+
* prolog, the inline searchable path, where the compiled helper prolog goes and
|
|
38
|
+
* that it is only declared when non-empty, the `<ochre>` wrapper, and the name
|
|
39
|
+
* of the variable holding the matching items. The body receives that name.
|
|
40
|
+
* @param parameters - The query parameters
|
|
41
|
+
* @param parameters.setScopeUuids - The Set scope UUIDs to search within
|
|
42
|
+
* @param parameters.belongsToCollectionScopeUuids - Collection scope UUIDs to narrow to
|
|
43
|
+
* @param parameters.queries - The query tree to compile, or null to match every item
|
|
44
|
+
* @param parameters.declarations - Extra prolog declarations, placed before the compiled prolog
|
|
45
|
+
* @param parameters.body - Builds the body from the name of the variable holding the items
|
|
46
|
+
* @returns A complete XQuery document
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
export declare function compileSetItemsQuery(parameters: {
|
|
50
|
+
setScopeUuids: ReadonlyArray<string>;
|
|
51
|
+
belongsToCollectionScopeUuids: ReadonlyArray<string>;
|
|
52
|
+
queries: Query | null;
|
|
53
|
+
declarations?: ReadonlyArray<string>;
|
|
54
|
+
body: (context: OchreQueryContext & {
|
|
55
|
+
items: string;
|
|
56
|
+
}) => string;
|
|
57
|
+
}): string;
|
|
58
|
+
/**
|
|
59
|
+
* Reduce a property-value facet query tree to the leaves that filter items
|
|
60
|
+
*
|
|
61
|
+
* A facet request carries property leaves that name a variable without naming
|
|
62
|
+
* a value, which select what to aggregate rather than which items to keep.
|
|
63
|
+
* Those are dropped, and groups left with a single child collapse into it.
|
|
64
|
+
* @param queries - The query tree to reduce
|
|
65
|
+
* @returns The reduced tree, or null when nothing filters items
|
|
66
|
+
* @internal
|
|
67
|
+
*/
|
|
68
|
+
export declare function getItemFilterQueries(queries: Query | null): Query | null;
|
|
69
|
+
/**
|
|
70
|
+
* Collect the property variables a query tree asks to be aggregated
|
|
71
|
+
* @param queries - The query tree to walk
|
|
72
|
+
* @returns One selector per distinct property variable and relation pair
|
|
73
|
+
* @internal
|
|
74
|
+
*/
|
|
75
|
+
export declare function getPropertyFacetSelectors(queries: Query | null): Array<{
|
|
76
|
+
uuid: string;
|
|
77
|
+
relation: PropertyRelation | null;
|
|
78
|
+
}>;
|
|
31
79
|
//#endregion
|