@tradik/xslt3 0.0.0-stage → 1.0.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/LICENSE.md +28 -0
- package/README.md +98 -2
- package/package.json +43 -4
- package/src/errors.js +20 -0
- package/src/functions/aggregates.js +144 -0
- package/src/functions/arraySort.js +33 -0
- package/src/functions/arrays.js +181 -0
- package/src/functions/attributeTypes.js +58 -0
- package/src/functions/baseUris.js +82 -0
- package/src/functions/collations.js +127 -0
- package/src/functions/collections.js +84 -0
- package/src/functions/context.js +83 -0
- package/src/functions/core.js +167 -0
- package/src/functions/datetime.js +203 -0
- package/src/functions/deepEqual.js +132 -0
- package/src/functions/diagnostics.js +88 -0
- package/src/functions/focus.js +34 -0
- package/src/functions/format/dateComponents.js +176 -0
- package/src/functions/format/datePicture.js +115 -0
- package/src/functions/format/decimalFormat.js +65 -0
- package/src/functions/format/digitPattern.js +145 -0
- package/src/functions/format/formatDateTime.js +204 -0
- package/src/functions/format/formatInteger.js +109 -0
- package/src/functions/format/formatNumber.js +165 -0
- package/src/functions/format/fractionFormat.js +80 -0
- package/src/functions/format/germanWords.js +89 -0
- package/src/functions/format/numberLanguages.js +41 -0
- package/src/functions/format/numberPicture.js +202 -0
- package/src/functions/format/numberWords.js +155 -0
- package/src/functions/format/place.js +74 -0
- package/src/functions/format/timezoneFormat.js +89 -0
- package/src/functions/higherOrder.js +106 -0
- package/src/functions/ids.js +127 -0
- package/src/functions/json/index.js +17 -0
- package/src/functions/json/jsonChars.js +110 -0
- package/src/functions/json/jsonParser.js +203 -0
- package/src/functions/json/jsonStrings.js +116 -0
- package/src/functions/json/jsonToXml.js +116 -0
- package/src/functions/json/parseJson.js +119 -0
- package/src/functions/json/xmlRepresentation.js +103 -0
- package/src/functions/json/xmlToJson.js +147 -0
- package/src/functions/library.js +47 -0
- package/src/functions/mapArrayEqual.js +41 -0
- package/src/functions/maps.js +152 -0
- package/src/functions/math.js +73 -0
- package/src/functions/nodePaths.js +181 -0
- package/src/functions/nodes.js +199 -0
- package/src/functions/numeric.js +148 -0
- package/src/functions/options.js +58 -0
- package/src/functions/parseIetfDate.js +104 -0
- package/src/functions/parseXml.js +77 -0
- package/src/functions/qnames.js +68 -0
- package/src/functions/random.js +128 -0
- package/src/functions/regex/analyzeString.js +144 -0
- package/src/functions/regex/charClass.js +112 -0
- package/src/functions/regex/compile.js +81 -0
- package/src/functions/regex/escapes.js +109 -0
- package/src/functions/regex/parser.js +169 -0
- package/src/functions/regex/reader.js +37 -0
- package/src/functions/regex/regexFunctions.js +140 -0
- package/src/functions/regex/translate.js +102 -0
- package/src/functions/regex/unicodeBlocks.js +194 -0
- package/src/functions/registry.js +140 -0
- package/src/functions/sequences.js +173 -0
- package/src/functions/serializeFunction.js +53 -0
- package/src/functions/signatures.js +105 -0
- package/src/functions/sort.js +102 -0
- package/src/functions/stringMatch.js +45 -0
- package/src/functions/strings.js +194 -0
- package/src/functions/stringsCore.js +66 -0
- package/src/functions/support.js +106 -0
- package/src/functions/textDecoding.js +117 -0
- package/src/functions/ucaCollation.js +127 -0
- package/src/functions/unparsedText.js +71 -0
- package/src/functions/uri.js +166 -0
- package/src/functions/xqueryModule.js +24 -0
- package/src/index.js +38 -0
- package/src/items/array.js +84 -0
- package/src/items/function.js +45 -0
- package/src/items/map.js +120 -0
- package/src/items/mapKey.js +82 -0
- package/src/serialize/index.js +129 -0
- package/src/serialize/markup/content.js +74 -0
- package/src/serialize/markup/dialect.js +178 -0
- package/src/serialize/markup/htmlData.js +70 -0
- package/src/serialize/markup/namespaces.js +145 -0
- package/src/serialize/markup/prolog.js +91 -0
- package/src/serialize/markup/writer.js +183 -0
- package/src/serialize/methods/adaptive.js +131 -0
- package/src/serialize/methods/json.js +158 -0
- package/src/serialize/methods/text.js +43 -0
- package/src/serialize/output/buffer.js +48 -0
- package/src/serialize/output/encoding.js +146 -0
- package/src/serialize/output/expander.js +204 -0
- package/src/serialize/params/fromElement.js +141 -0
- package/src/serialize/params/fromMap.js +128 -0
- package/src/serialize/params/names.js +69 -0
- package/src/serialize/params/settings.js +153 -0
- package/src/serialize/params/values.js +163 -0
- package/src/serialize/sequence.js +111 -0
- package/src/xdm/arithmetic.js +155 -0
- package/src/xdm/atomic.js +142 -0
- package/src/xdm/binary.js +92 -0
- package/src/xdm/calendar.js +71 -0
- package/src/xdm/cast.js +176 -0
- package/src/xdm/compare.js +192 -0
- package/src/xdm/datetime.js +206 -0
- package/src/xdm/decimal.js +200 -0
- package/src/xdm/duration.js +128 -0
- package/src/xdm/generalCompare.js +152 -0
- package/src/xdm/index.js +47 -0
- package/src/xdm/lexical.js +126 -0
- package/src/xdm/nodes.js +170 -0
- package/src/xdm/numeric.js +122 -0
- package/src/xdm/qname.js +65 -0
- package/src/xdm/strings.js +52 -0
- package/src/xdm/temporalArithmetic.js +180 -0
- package/src/xdm/timeline.js +68 -0
- package/src/xdm/types.js +178 -0
- package/src/xpath/eval/atomics.js +45 -0
- package/src/xpath/eval/axes.js +212 -0
- package/src/xpath/eval/calls.js +168 -0
- package/src/xpath/eval/coercion.js +146 -0
- package/src/xpath/eval/compiler.js +71 -0
- package/src/xpath/eval/constructors.js +129 -0
- package/src/xpath/eval/decimalFormats.js +93 -0
- package/src/xpath/eval/descendantMemo.js +77 -0
- package/src/xpath/eval/documentOrder.js +142 -0
- package/src/xpath/eval/domNodes.js +174 -0
- package/src/xpath/eval/dynamicCall.js +27 -0
- package/src/xpath/eval/dynamicContext.js +161 -0
- package/src/xpath/eval/flow.js +115 -0
- package/src/xpath/eval/functionItems.js +194 -0
- package/src/xpath/eval/groupedChildren.js +88 -0
- package/src/xpath/eval/inline.js +71 -0
- package/src/xpath/eval/namespaceNodes.js +90 -0
- package/src/xpath/eval/nodeTests.js +164 -0
- package/src/xpath/eval/operators.js +190 -0
- package/src/xpath/eval/paths.js +163 -0
- package/src/xpath/eval/positional.js +81 -0
- package/src/xpath/eval/predicates.js +56 -0
- package/src/xpath/eval/primary.js +99 -0
- package/src/xpath/eval/resources.js +149 -0
- package/src/xpath/eval/scope.js +108 -0
- package/src/xpath/eval/sequenceType.js +193 -0
- package/src/xpath/eval/staticContext.js +142 -0
- package/src/xpath/eval/steps.js +98 -0
- package/src/xpath/eval/subtype.js +154 -0
- package/src/xpath/eval/typeExprs.js +147 -0
- package/src/xpath/eval/typeNames.js +101 -0
- package/src/xpath/eval/uris.js +21 -0
- package/src/xpath/eval/values.js +64 -0
- package/src/xpath/index.js +217 -0
- package/src/xpath/syntax/ast.js +130 -0
- package/src/xpath/syntax/chars.js +58 -0
- package/src/xpath/syntax/constructors.js +144 -0
- package/src/xpath/syntax/expressions.js +107 -0
- package/src/xpath/syntax/index.js +44 -0
- package/src/xpath/syntax/kindTests.js +134 -0
- package/src/xpath/syntax/lexer.js +220 -0
- package/src/xpath/syntax/operators.js +111 -0
- package/src/xpath/syntax/paths.js +102 -0
- package/src/xpath/syntax/postfix.js +91 -0
- package/src/xpath/syntax/primaries.js +149 -0
- package/src/xpath/syntax/steps.js +146 -0
- package/src/xpath/syntax/syntaxError.js +31 -0
- package/src/xpath/syntax/tokenStream.js +163 -0
- package/src/xpath/syntax/typeAst.js +76 -0
- package/src/xpath/syntax/typeOperators.js +127 -0
- package/src/xpath/syntax/types.js +158 -0
- package/src/xslt/api.js +153 -0
- package/src/xslt/compiler/acceptance.js +178 -0
- package/src/xslt/compiler/accumulatorDecl.js +146 -0
- package/src/xslt/compiler/attributeValues.js +174 -0
- package/src/xslt/compiler/attributes.js +211 -0
- package/src/xslt/compiler/avt.js +103 -0
- package/src/xslt/compiler/body.js +154 -0
- package/src/xslt/compiler/children.js +105 -0
- package/src/xslt/compiler/compatibility.js +130 -0
- package/src/xslt/compiler/components.js +179 -0
- package/src/xslt/compiler/contextItem.js +95 -0
- package/src/xslt/compiler/decimalFormats.js +132 -0
- package/src/xslt/compiler/declarationKinds.js +82 -0
- package/src/xslt/compiler/elementInfo.js +246 -0
- package/src/xslt/compiler/expressions.js +175 -0
- package/src/xslt/compiler/globalContextItem.js +92 -0
- package/src/xslt/compiler/globals.js +209 -0
- package/src/xslt/compiler/linker.js +134 -0
- package/src/xslt/compiler/miscDecl.js +171 -0
- package/src/xslt/compiler/modeDecl.js +88 -0
- package/src/xslt/compiler/moduleLoading.js +111 -0
- package/src/xslt/compiler/modules.js +158 -0
- package/src/xslt/compiler/outputDecl.js +209 -0
- package/src/xslt/compiler/overrideLinks.js +141 -0
- package/src/xslt/compiler/packageChecks.js +84 -0
- package/src/xslt/compiler/packageRegistry.js +95 -0
- package/src/xslt/compiler/packageTable.js +181 -0
- package/src/xslt/compiler/packageVersions.js +143 -0
- package/src/xslt/compiler/packages.js +138 -0
- package/src/xslt/compiler/registry.js +234 -0
- package/src/xslt/compiler/staticStage.js +197 -0
- package/src/xslt/compiler/staticVariables.js +93 -0
- package/src/xslt/compiler/stylesheet.js +205 -0
- package/src/xslt/compiler/tailPosition.js +74 -0
- package/src/xslt/compiler/templatesDecl.js +180 -0
- package/src/xslt/compiler/useAccumulators.js +76 -0
- package/src/xslt/compiler/useWhen.js +94 -0
- package/src/xslt/compiler/visibility.js +160 -0
- package/src/xslt/index.js +11 -0
- package/src/xslt/instructions/analyzeString.js +137 -0
- package/src/xslt/instructions/assert.js +72 -0
- package/src/xslt/instructions/attributeSets.js +115 -0
- package/src/xslt/instructions/conditional.js +99 -0
- package/src/xslt/instructions/control.js +123 -0
- package/src/xslt/instructions/copy.js +151 -0
- package/src/xslt/instructions/elements.js +161 -0
- package/src/xslt/instructions/evaluate.js +189 -0
- package/src/xslt/instructions/fork.js +35 -0
- package/src/xslt/instructions/grouping.js +106 -0
- package/src/xslt/instructions/index.js +94 -0
- package/src/xslt/instructions/iterate.js +182 -0
- package/src/xslt/instructions/literal.js +142 -0
- package/src/xslt/instructions/maps.js +72 -0
- package/src/xslt/instructions/merge.js +190 -0
- package/src/xslt/instructions/number.js +205 -0
- package/src/xslt/instructions/output.js +102 -0
- package/src/xslt/instructions/params.js +110 -0
- package/src/xslt/instructions/resultDocument.js +143 -0
- package/src/xslt/instructions/sort.js +114 -0
- package/src/xslt/instructions/sourceDocument.js +73 -0
- package/src/xslt/instructions/templates.js +153 -0
- package/src/xslt/instructions/text.js +151 -0
- package/src/xslt/instructions/try.js +186 -0
- package/src/xslt/instructions/variables.js +119 -0
- package/src/xslt/names.js +204 -0
- package/src/xslt/patterns/axisOrigins.js +121 -0
- package/src/xslt/patterns/compile.js +151 -0
- package/src/xslt/patterns/general.js +132 -0
- package/src/xslt/patterns/grammar.js +190 -0
- package/src/xslt/patterns/modes.js +153 -0
- package/src/xslt/patterns/paths.js +130 -0
- package/src/xslt/patterns/patternChecks.js +29 -0
- package/src/xslt/patterns/steps.js +162 -0
- package/src/xslt/processor.js +178 -0
- package/src/xslt/runtime/accumulatorFunctions.js +48 -0
- package/src/xslt/runtime/accumulators.js +174 -0
- package/src/xslt/runtime/apply.js +153 -0
- package/src/xslt/runtime/atomicKeys.js +36 -0
- package/src/xslt/runtime/availability.js +162 -0
- package/src/xslt/runtime/baseUri.js +64 -0
- package/src/xslt/runtime/compilerHook.js +27 -0
- package/src/xslt/runtime/conditional.js +110 -0
- package/src/xslt/runtime/context.js +106 -0
- package/src/xslt/runtime/copy.js +123 -0
- package/src/xslt/runtime/copyFunctions.js +71 -0
- package/src/xslt/runtime/copyOrigins.js +60 -0
- package/src/xslt/runtime/defaultMethod.js +42 -0
- package/src/xslt/runtime/documentContent.js +93 -0
- package/src/xslt/runtime/dynamicXPath.js +90 -0
- package/src/xslt/runtime/functionCache.js +65 -0
- package/src/xslt/runtime/functionCall.js +57 -0
- package/src/xslt/runtime/functions.js +191 -0
- package/src/xslt/runtime/groups.js +108 -0
- package/src/xslt/runtime/invocation.js +156 -0
- package/src/xslt/runtime/iteration.js +107 -0
- package/src/xslt/runtime/keys.js +141 -0
- package/src/xslt/runtime/lazy.js +97 -0
- package/src/xslt/runtime/locations.js +86 -0
- package/src/xslt/runtime/machine.js +137 -0
- package/src/xslt/runtime/merging.js +167 -0
- package/src/xslt/runtime/namespaceScope.js +141 -0
- package/src/xslt/runtime/numberFormat.js +89 -0
- package/src/xslt/runtime/numbering.js +168 -0
- package/src/xslt/runtime/params.js +59 -0
- package/src/xslt/runtime/properties.js +103 -0
- package/src/xslt/runtime/rawResults.js +82 -0
- package/src/xslt/runtime/sequenceReceiver.js +102 -0
- package/src/xslt/runtime/snapshot.js +66 -0
- package/src/xslt/runtime/sortKeys.js +119 -0
- package/src/xslt/runtime/streamAvailable.js +60 -0
- package/src/xslt/runtime/strip.js +102 -0
- package/src/xslt/runtime/transformFunction.js +169 -0
- package/src/xslt/runtime/transformation.js +168 -0
- package/src/xslt/runtime/transformationContext.js +85 -0
- package/src/xslt/runtime/treeReceiver.js +194 -0
- package/src/xslt/runtime/values.js +92 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hooks of the dynamic context that reach outside the expression: text
|
|
3
|
+
* resources (fn:unparsed-text, fn:json-doc), XML parsing (fn:parse-xml)
|
|
4
|
+
* and collections (fn:collection, fn:uri-collection). Each is built from
|
|
5
|
+
* a dynamic option, with a default:
|
|
6
|
+
*
|
|
7
|
+
* - `textLoader(uri)`: returns the resource as a string, bytes, or
|
|
8
|
+
* `{content, encoding, mediaType}`; default: no resources, so an
|
|
9
|
+
* expression cannot read files unless the host allows it (pass
|
|
10
|
+
* `readFileUri` to read `file:` URIs in Node.js);
|
|
11
|
+
* - `xmlParser(text, baseUri)`: returns a Document, throws when the text
|
|
12
|
+
* is not well-formed; default: `globalThis.DOMParser` when present;
|
|
13
|
+
* - `collections(uri)`: returns the items of a collection (JavaScript
|
|
14
|
+
* values are converted as variables are), `uri` null for the default
|
|
15
|
+
* collection; default: no collections.
|
|
16
|
+
*
|
|
17
|
+
* @module @tradik/xslt3/xpath/eval/resources
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { XPathError } from "../../errors.js";
|
|
21
|
+
import { decodeText } from "../../functions/textDecoding.js";
|
|
22
|
+
import { toSequence } from "./values.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Reads `file:` URIs with node:fs (Node.js 22.3 and later). Not used by
|
|
26
|
+
* default: pass it as the `textLoader` option to let expressions read
|
|
27
|
+
* local files, only when the expressions are trusted.
|
|
28
|
+
* @param {string} uri - Absolute URI
|
|
29
|
+
* @returns {Uint8Array|null} the bytes, null for other schemes or
|
|
30
|
+
* without node:fs
|
|
31
|
+
*/
|
|
32
|
+
export function readFileUri(uri) {
|
|
33
|
+
const fs = globalThis.process?.getBuiltinModule?.("node:fs");
|
|
34
|
+
if (!fs || !uri.startsWith("file:")) return null;
|
|
35
|
+
return fs.readFileSync(new globalThis.URL(uri));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The default text loader: no resource is available.
|
|
40
|
+
* @returns {null}
|
|
41
|
+
*/
|
|
42
|
+
function noResources() {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The absolute URI of a resource reference.
|
|
48
|
+
* @param {string} href
|
|
49
|
+
* @param {string|undefined} baseUri - Static base URI
|
|
50
|
+
* @returns {string}
|
|
51
|
+
* @throws {XPathError} FOUT1170 for a fragment identifier, an invalid URI
|
|
52
|
+
* or a relative URI without base
|
|
53
|
+
*/
|
|
54
|
+
export function resourceUri(href, baseUri) {
|
|
55
|
+
if (href.includes("#") || /%(?![0-9A-Fa-f]{2})/.test(href)) {
|
|
56
|
+
throw new XPathError("FOUT1170", `Invalid resource URI ${href}`);
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return new globalThis.URL(href, baseUri).href;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw new XPathError("FOUT1170", `Cannot resolve the URI ${href}`, {
|
|
62
|
+
cause: error,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The text loading hook: resolves, loads (once per URI and encoding
|
|
69
|
+
* during an evaluation) and decodes.
|
|
70
|
+
* @param {((uri: string) => import("../../functions/textDecoding.js").TextResource)|undefined} loader
|
|
71
|
+
* @param {string|undefined} baseUri
|
|
72
|
+
* @returns {(href: string, encoding?: string, base?: string) => string}
|
|
73
|
+
* the hook; `base` replaces the base URI of relative references
|
|
74
|
+
*/
|
|
75
|
+
export function createTextLoader(loader = noResources, baseUri) {
|
|
76
|
+
const texts = new Map();
|
|
77
|
+
return (href, encoding, base = baseUri) => {
|
|
78
|
+
const uri = resourceUri(href, base);
|
|
79
|
+
const key = `${encoding ?? ""} ${uri}`;
|
|
80
|
+
if (texts.has(key)) return texts.get(key);
|
|
81
|
+
let resource;
|
|
82
|
+
try {
|
|
83
|
+
resource = loader(uri);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
throw new XPathError("FOUT1170", `Cannot read ${uri}`, { cause: error });
|
|
86
|
+
}
|
|
87
|
+
if (resource === null || resource === undefined) {
|
|
88
|
+
throw new XPathError("FOUT1170", `No resource at ${uri}`);
|
|
89
|
+
}
|
|
90
|
+
const text = decodeText(resource, encoding);
|
|
91
|
+
texts.set(key, text);
|
|
92
|
+
return text;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The XML parsing hook.
|
|
98
|
+
* @param {((text: string, baseUri?: string) => Document)|undefined} parser
|
|
99
|
+
* @returns {(text: string, baseUri?: string) => Document}
|
|
100
|
+
*/
|
|
101
|
+
export function createXmlParser(parser) {
|
|
102
|
+
if (parser) return parser;
|
|
103
|
+
return (text) => {
|
|
104
|
+
const DOMParser = globalThis.DOMParser;
|
|
105
|
+
if (!DOMParser) {
|
|
106
|
+
throw new XPathError(
|
|
107
|
+
"FODC0006",
|
|
108
|
+
"No XML parser: pass the xmlParser option",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
const document = new DOMParser().parseFromString(text, "application/xml");
|
|
112
|
+
if (document.getElementsByTagName("parsererror").length) {
|
|
113
|
+
throw new XPathError("FODC0006", "The string is not well-formed XML");
|
|
114
|
+
}
|
|
115
|
+
return document;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The collection hook.
|
|
121
|
+
* @param {((uri: string|null) => Array|null|undefined)|undefined} collections
|
|
122
|
+
* @param {string|undefined} baseUri
|
|
123
|
+
* @returns {(href: string|null, base?: string) => Array} the hook; `base`
|
|
124
|
+
* replaces the base URI of relative references
|
|
125
|
+
* @throws {XPathError} FODC0002 for an unknown collection, FODC0004 for an
|
|
126
|
+
* invalid URI
|
|
127
|
+
*/
|
|
128
|
+
export function createCollections(collections, baseUri) {
|
|
129
|
+
return (href, base = baseUri) => {
|
|
130
|
+
let uri = null;
|
|
131
|
+
if (href !== null) {
|
|
132
|
+
try {
|
|
133
|
+
uri = new globalThis.URL(href, base).href;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
throw new XPathError("FODC0004", `Invalid collection URI ${href}`, {
|
|
136
|
+
cause: error,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const items = collections?.(uri);
|
|
141
|
+
if (!items) {
|
|
142
|
+
throw new XPathError(
|
|
143
|
+
"FODC0002",
|
|
144
|
+
uri === null ? "No default collection" : `No collection ${uri}`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
return toSequence(items);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variables and focus at compile time and at run time.
|
|
3
|
+
*
|
|
4
|
+
* Compile time: a scope is the static context plus the in-scope variables,
|
|
5
|
+
* a linked list from the innermost binding outwards. A variable reference
|
|
6
|
+
* is resolved once, to its depth in that list.
|
|
7
|
+
*
|
|
8
|
+
* Run time: an evaluator receives a context `{item, position, size, env,
|
|
9
|
+
* dyn}`: the focus (item undefined when absent), the environment (a linked
|
|
10
|
+
* list of values matching the compile-time scope, so a reference walks
|
|
11
|
+
* `depth` links) and the dynamic context shared by the whole evaluation.
|
|
12
|
+
*
|
|
13
|
+
* @module @tradik/xslt3/xpath/eval/scope
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { XPathError } from "../../errors.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {object} Scope
|
|
20
|
+
* @property {import("./staticContext.js").StaticContext} sc
|
|
21
|
+
* @property {{key: string, next: object}|null} vars - Innermost binding first
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {object} Context
|
|
26
|
+
* @property {*} item - Context item, undefined when the focus is absent
|
|
27
|
+
* @property {number} position - Context position
|
|
28
|
+
* @property {number} size - Context size
|
|
29
|
+
* @property {{value: Array, next: object}|null} env - Variable values
|
|
30
|
+
* @property {object} dyn - Dynamic context (see dynamicContext.js)
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {(ctx: Context) => Array} Evaluator - A compiled expression
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {import("./staticContext.js").StaticContext} sc
|
|
39
|
+
* @param {string[]} keys - Clark names of the external variables, the
|
|
40
|
+
* first one outermost
|
|
41
|
+
* @returns {Scope}
|
|
42
|
+
*/
|
|
43
|
+
export function rootScope(sc, keys) {
|
|
44
|
+
let scope = { sc, vars: null };
|
|
45
|
+
for (const key of keys) scope = bindVariable(scope, key);
|
|
46
|
+
return scope;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {Scope} scope
|
|
51
|
+
* @param {string} key - Clark name of the new variable
|
|
52
|
+
* @returns {Scope} the scope with the variable added innermost
|
|
53
|
+
*/
|
|
54
|
+
export function bindVariable(scope, key) {
|
|
55
|
+
return { sc: scope.sc, vars: { key, next: scope.vars } };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {Scope} scope
|
|
60
|
+
* @param {string} key - Clark name
|
|
61
|
+
* @returns {number} depth of the innermost binding of the name, -1 when
|
|
62
|
+
* the variable is not in scope
|
|
63
|
+
*/
|
|
64
|
+
export function variableDepth(scope, key) {
|
|
65
|
+
let depth = 0;
|
|
66
|
+
for (let v = scope.vars; v !== null; v = v.next, depth++) {
|
|
67
|
+
if (v.key === key) return depth;
|
|
68
|
+
}
|
|
69
|
+
return -1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {Context} ctx
|
|
74
|
+
* @param {Array} value
|
|
75
|
+
* @returns {Context} the context with a value bound innermost
|
|
76
|
+
*/
|
|
77
|
+
export function withVariable(ctx, value) {
|
|
78
|
+
return {
|
|
79
|
+
item: ctx.item,
|
|
80
|
+
position: ctx.position,
|
|
81
|
+
size: ctx.size,
|
|
82
|
+
env: { value, next: ctx.env },
|
|
83
|
+
dyn: ctx.dyn,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {Context} ctx
|
|
89
|
+
* @param {*} item
|
|
90
|
+
* @param {number} position
|
|
91
|
+
* @param {number} size
|
|
92
|
+
* @returns {Context} the context with a new focus
|
|
93
|
+
*/
|
|
94
|
+
export function withFocus(ctx, item, position, size) {
|
|
95
|
+
return { item, position, size, env: ctx.env, dyn: ctx.dyn };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @param {Context} ctx
|
|
100
|
+
* @returns {*} the context item
|
|
101
|
+
* @throws {XPathError} XPDY0002 when the focus is absent
|
|
102
|
+
*/
|
|
103
|
+
export function contextItem(ctx) {
|
|
104
|
+
if (ctx.item === undefined) {
|
|
105
|
+
throw new XPathError("XPDY0002", "The context item is absent");
|
|
106
|
+
}
|
|
107
|
+
return ctx.item;
|
|
108
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sequence types (XPath 3.1 section 2.5) compiled from the AST, and
|
|
3
|
+
* sequence type matching (section 2.5.5): `instance of`, `treat as`,
|
|
4
|
+
* typed variables and function signatures.
|
|
5
|
+
*
|
|
6
|
+
* A compiled sequence type is `{itemType, occurrence}` with `itemType`
|
|
7
|
+
* null for empty-sequence(). A compiled item type has a `kind` ("item",
|
|
8
|
+
* "atomic", "node", "anyFunction", "function", "anyMap", "map",
|
|
9
|
+
* "anyArray", "array"), the fields of that kind and `matches(item)`.
|
|
10
|
+
*
|
|
11
|
+
* @module @tradik/xslt3/xpath/eval/sequenceType
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
isArray,
|
|
16
|
+
isAtomic,
|
|
17
|
+
isFunctionItem,
|
|
18
|
+
isMap,
|
|
19
|
+
isNode,
|
|
20
|
+
} from "../../xdm/atomic.js";
|
|
21
|
+
import { derivesFrom, types } from "../../xdm/types.js";
|
|
22
|
+
import { compileKindTest } from "./nodeTests.js";
|
|
23
|
+
import { isSubtype } from "./subtype.js";
|
|
24
|
+
import { resolveAtomicType } from "./typeNames.js";
|
|
25
|
+
|
|
26
|
+
/** @typedef {{itemType: object|null, occurrence: string}} SequenceType */
|
|
27
|
+
|
|
28
|
+
/** item()* */
|
|
29
|
+
export const ANY_SEQUENCE = Object.freeze({
|
|
30
|
+
itemType: Object.freeze({ kind: "item", matches: () => true }),
|
|
31
|
+
occurrence: "*",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Atomic item type of a list of member types.
|
|
36
|
+
* @param {object[]} members - Atomic type descriptors
|
|
37
|
+
* @returns {object} the item type
|
|
38
|
+
*/
|
|
39
|
+
export function atomicItemType(members) {
|
|
40
|
+
const matches =
|
|
41
|
+
members.length === 1
|
|
42
|
+
? (item) => isAtomic(item) && derivesFrom(item.type, members[0])
|
|
43
|
+
: (item) =>
|
|
44
|
+
isAtomic(item) && members.some((t) => derivesFrom(item.type, t));
|
|
45
|
+
return { kind: "atomic", types: members, matches };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Signature of every map as a function: function(xs:anyAtomicType) as item()* */
|
|
49
|
+
export const MAP_SIGNATURE = Object.freeze({
|
|
50
|
+
params: [{ itemType: atomicItemType([types.anyAtomicType]), occurrence: "" }],
|
|
51
|
+
returns: ANY_SEQUENCE,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/** Signature of every array as a function: function(xs:integer) as item()* */
|
|
55
|
+
export const ARRAY_SIGNATURE = Object.freeze({
|
|
56
|
+
params: [{ itemType: atomicItemType([types.integer]), occurrence: "" }],
|
|
57
|
+
returns: ANY_SEQUENCE,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {number} length - Number of items
|
|
62
|
+
* @param {string} occurrence - "", "?", "*" or "+"
|
|
63
|
+
* @returns {boolean} whether the number of items is allowed
|
|
64
|
+
*/
|
|
65
|
+
export function occurrenceAllows(length, occurrence) {
|
|
66
|
+
if (length === 1) return true;
|
|
67
|
+
if (length === 0) return occurrence === "?" || occurrence === "*";
|
|
68
|
+
return occurrence === "*" || occurrence === "+";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {Array} sequence
|
|
73
|
+
* @param {SequenceType} type
|
|
74
|
+
* @returns {boolean} whether the sequence matches the type
|
|
75
|
+
*/
|
|
76
|
+
export function matchesSequenceType(sequence, type) {
|
|
77
|
+
if (type.itemType === null) return sequence.length === 0;
|
|
78
|
+
if (!occurrenceAllows(sequence.length, type.occurrence)) return false;
|
|
79
|
+
const { matches } = type.itemType;
|
|
80
|
+
for (const item of sequence) if (!matches(item)) return false;
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {*} item - A function item, map or array
|
|
86
|
+
* @returns {{params: SequenceType[], returns: SequenceType}} its signature
|
|
87
|
+
*/
|
|
88
|
+
export function signatureOf(item) {
|
|
89
|
+
if (isMap(item)) return MAP_SIGNATURE;
|
|
90
|
+
if (isArray(item)) return ARRAY_SIGNATURE;
|
|
91
|
+
return item.signature;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Whether a map or an array matches a typed function test (XPath 3.1
|
|
96
|
+
* 2.5.5.7): by its actual entries, as a function of one key or position.
|
|
97
|
+
* @param {*} item - Map or array
|
|
98
|
+
* @param {object} test - Compiled TypedFunctionTest
|
|
99
|
+
* @returns {boolean}
|
|
100
|
+
*/
|
|
101
|
+
function lookupMatches(item, test) {
|
|
102
|
+
if (test.params.length !== 1) return false;
|
|
103
|
+
const [param] = test.params;
|
|
104
|
+
const { returns } = test;
|
|
105
|
+
if (isMap(item)) {
|
|
106
|
+
return (
|
|
107
|
+
isSubtype(param, MAP_SIGNATURE.params[0]) &&
|
|
108
|
+
matchesSequenceType([], returns) &&
|
|
109
|
+
[...item.entries.values()].every(({ value }) =>
|
|
110
|
+
matchesSequenceType(value, returns),
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return (
|
|
115
|
+
isSubtype(param, ARRAY_SIGNATURE.params[0]) &&
|
|
116
|
+
item.members.every((member) => matchesSequenceType(member, returns))
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Item type compilers by AST node type. */
|
|
121
|
+
const itemTypes = {
|
|
122
|
+
AnyItemTest: () => ANY_SEQUENCE.itemType,
|
|
123
|
+
AtomicType: (node, sc) => atomicItemType(resolveAtomicType(node.name, sc)),
|
|
124
|
+
AnyFunctionTest: () => ({ kind: "anyFunction", matches: isFunctionItem }),
|
|
125
|
+
AnyMapTest: () => ({ kind: "anyMap", matches: isMap }),
|
|
126
|
+
AnyArrayTest: () => ({ kind: "anyArray", matches: isArray }),
|
|
127
|
+
TypedFunctionTest(node, sc) {
|
|
128
|
+
const type = {
|
|
129
|
+
kind: "function",
|
|
130
|
+
params: node.paramTypes.map((p) => compileSequenceType(p, sc)),
|
|
131
|
+
returns: compileSequenceType(node.returnType, sc),
|
|
132
|
+
};
|
|
133
|
+
type.matches = (item) => {
|
|
134
|
+
if (!isFunctionItem(item)) return false;
|
|
135
|
+
if (isMap(item) || isArray(item)) return lookupMatches(item, type);
|
|
136
|
+
const signature = signatureOf(item);
|
|
137
|
+
if (signature.params.length !== type.params.length) return false;
|
|
138
|
+
return isSubtype(
|
|
139
|
+
{ itemType: { kind: "function", ...signature }, occurrence: "" },
|
|
140
|
+
{ itemType: type, occurrence: "" },
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
return type;
|
|
144
|
+
},
|
|
145
|
+
TypedMapTest(node, sc) {
|
|
146
|
+
const keyType = compileItemType(node.keyType, sc);
|
|
147
|
+
const valueType = compileSequenceType(node.valueType, sc);
|
|
148
|
+
const matches = (item) =>
|
|
149
|
+
isMap(item) &&
|
|
150
|
+
[...item.entries.values()].every(
|
|
151
|
+
({ key, value }) =>
|
|
152
|
+
keyType.matches(key) && matchesSequenceType(value, valueType),
|
|
153
|
+
);
|
|
154
|
+
return { kind: "map", keyType, valueType, matches };
|
|
155
|
+
},
|
|
156
|
+
TypedArrayTest(node, sc) {
|
|
157
|
+
const memberType = compileSequenceType(node.memberType, sc);
|
|
158
|
+
const matches = (item) =>
|
|
159
|
+
isArray(item) &&
|
|
160
|
+
item.members.every((member) => matchesSequenceType(member, memberType));
|
|
161
|
+
return { kind: "array", memberType, matches };
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @param {import("../syntax/typeAst.js").ItemType} node
|
|
167
|
+
* @param {import("./staticContext.js").StaticContext} sc
|
|
168
|
+
* @returns {object} the compiled item type
|
|
169
|
+
* @throws {XPathError} XPST0051 (unknown atomic type), XPST0008, XPST0081
|
|
170
|
+
*/
|
|
171
|
+
export function compileItemType(node, sc) {
|
|
172
|
+
const compiler = itemTypes[node.type];
|
|
173
|
+
if (compiler) return compiler(node, sc);
|
|
174
|
+
const test = compileKindTest(node, sc);
|
|
175
|
+
return {
|
|
176
|
+
kind: "node",
|
|
177
|
+
test,
|
|
178
|
+
matches: (item) => isNode(item) && test.matches(item),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* @param {import("../syntax/typeAst.js").SequenceType} node
|
|
184
|
+
* @param {import("./staticContext.js").StaticContext} sc
|
|
185
|
+
* @returns {SequenceType}
|
|
186
|
+
*/
|
|
187
|
+
export function compileSequenceType(node, sc) {
|
|
188
|
+
return {
|
|
189
|
+
itemType:
|
|
190
|
+
node.itemType === null ? null : compileItemType(node.itemType, sc),
|
|
191
|
+
occurrence: node.occurrence,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The static context of an XPath expression (XPath 3.1 section 2.1.1) built
|
|
3
|
+
* from the options of compileXPath, and the resolution of the names written
|
|
4
|
+
* in an expression to expanded names.
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/xpath/eval/staticContext
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { XPathError } from "../../errors.js";
|
|
10
|
+
import { NS } from "../../functions/signatures.js";
|
|
11
|
+
import { XS_NAMESPACE } from "../../xdm/types.js";
|
|
12
|
+
import { createDecimalFormats } from "./decimalFormats.js";
|
|
13
|
+
|
|
14
|
+
/** URI of the Unicode codepoint collation, the only one supported. */
|
|
15
|
+
export const CODEPOINT_COLLATION =
|
|
16
|
+
"http://www.w3.org/2005/xpath-functions/collation/codepoint";
|
|
17
|
+
|
|
18
|
+
/** Namespace of the error codes. */
|
|
19
|
+
export const ERR_NAMESPACE = "http://www.w3.org/2005/xqt-errors";
|
|
20
|
+
|
|
21
|
+
/** Namespaces predeclared in every static context. */
|
|
22
|
+
export const PREDECLARED_NAMESPACES = Object.freeze({
|
|
23
|
+
xml: "http://www.w3.org/XML/1998/namespace",
|
|
24
|
+
xs: XS_NAMESPACE,
|
|
25
|
+
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
|
26
|
+
fn: NS.fn,
|
|
27
|
+
math: NS.math,
|
|
28
|
+
map: NS.map,
|
|
29
|
+
array: NS.array,
|
|
30
|
+
err: ERR_NAMESPACE,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {object} StaticContext
|
|
35
|
+
* @property {Map<string, string>} namespaces - Prefix to URI
|
|
36
|
+
* @property {string} defaultElementNamespace - "" for none
|
|
37
|
+
* @property {string} defaultFunctionNamespace
|
|
38
|
+
* @property {object} functions - Function library (functions/registry.js)
|
|
39
|
+
* @property {string|undefined} baseUri - Static base URI
|
|
40
|
+
* @property {string} defaultCollation - Collation URI
|
|
41
|
+
* @property {boolean} backwardsCompatible - XPath 1.0 compatibility mode
|
|
42
|
+
* @property {{get: (name: string) => object|undefined}} decimalFormats -
|
|
43
|
+
* Decimal formats by name (see decimalFormats.js)
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Normalizes namespace bindings given as an object, a Map or an array of
|
|
48
|
+
* `{prefix, uri}`.
|
|
49
|
+
* @param {object|Map|Array|undefined} namespaces
|
|
50
|
+
* @returns {Array<[string, string]>}
|
|
51
|
+
*/
|
|
52
|
+
function namespacePairs(namespaces) {
|
|
53
|
+
if (!namespaces) return [];
|
|
54
|
+
if (Array.isArray(namespaces)) {
|
|
55
|
+
return namespaces.map(({ prefix, uri }) => [prefix ?? "", uri]);
|
|
56
|
+
}
|
|
57
|
+
if (namespaces instanceof Map) return [...namespaces];
|
|
58
|
+
return Object.entries(namespaces);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Builds a static context.
|
|
63
|
+
* @param {object} options - See compileXPath in xpath/index.js
|
|
64
|
+
* @param {object} functions - Function library
|
|
65
|
+
* @returns {StaticContext}
|
|
66
|
+
* @throws {XPathError} FOCH0002 for a default collation other than the
|
|
67
|
+
* codepoint collation
|
|
68
|
+
*/
|
|
69
|
+
export function createStaticContext(options, functions) {
|
|
70
|
+
const namespaces = new Map(Object.entries(PREDECLARED_NAMESPACES));
|
|
71
|
+
let defaultElementNamespace = options.defaultElementNamespace ?? "";
|
|
72
|
+
for (const [prefix, uri] of namespacePairs(options.namespaces)) {
|
|
73
|
+
if (prefix === "") defaultElementNamespace = uri;
|
|
74
|
+
else namespaces.set(prefix, uri);
|
|
75
|
+
}
|
|
76
|
+
const defaultCollation = options.defaultCollation ?? CODEPOINT_COLLATION;
|
|
77
|
+
if (defaultCollation !== CODEPOINT_COLLATION) {
|
|
78
|
+
throw new XPathError(
|
|
79
|
+
"FOCH0002",
|
|
80
|
+
`Unsupported collation ${defaultCollation}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const sc = {
|
|
84
|
+
namespaces,
|
|
85
|
+
defaultElementNamespace,
|
|
86
|
+
defaultFunctionNamespace: options.defaultFunctionNamespace ?? NS.fn,
|
|
87
|
+
functions,
|
|
88
|
+
baseUri: options.baseUri,
|
|
89
|
+
defaultCollation,
|
|
90
|
+
backwardsCompatible: Boolean(options.backwardsCompatible),
|
|
91
|
+
};
|
|
92
|
+
sc.decimalFormats = createDecimalFormats(options.decimalFormats, (name) =>
|
|
93
|
+
variableKey(name, sc),
|
|
94
|
+
);
|
|
95
|
+
return sc;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Resolves a name as written to its namespace URI.
|
|
100
|
+
* @param {import("../syntax/ast.js").QName} name
|
|
101
|
+
* @param {StaticContext} sc
|
|
102
|
+
* @param {string} defaultNamespace - URI for an unprefixed name
|
|
103
|
+
* @returns {string} the namespace URI ("" for none)
|
|
104
|
+
* @throws {XPathError} XPST0081 for an undeclared prefix
|
|
105
|
+
*/
|
|
106
|
+
export function namespaceOf(name, sc, defaultNamespace) {
|
|
107
|
+
if (name.uri !== null) return name.uri;
|
|
108
|
+
if (name.prefix === null) return defaultNamespace;
|
|
109
|
+
const uri = sc.namespaces.get(name.prefix);
|
|
110
|
+
if (uri === undefined) {
|
|
111
|
+
throw new XPathError(
|
|
112
|
+
"XPST0081",
|
|
113
|
+
`The prefix ${name.prefix} is not declared`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return uri;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Expanded name in Clark notation, `{uri}local`.
|
|
121
|
+
* @param {string} uri
|
|
122
|
+
* @param {string} local
|
|
123
|
+
* @returns {string}
|
|
124
|
+
*/
|
|
125
|
+
export const clark = (uri, local) => `{${uri}}${local}`;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Parses a variable name given in the options: `Q{uri}local`,
|
|
129
|
+
* `prefix:local` (resolved with the static namespaces) or `local`.
|
|
130
|
+
* @param {string} text
|
|
131
|
+
* @param {StaticContext} sc
|
|
132
|
+
* @returns {string} Clark name
|
|
133
|
+
* @throws {XPathError} XPST0081 for an undeclared prefix
|
|
134
|
+
*/
|
|
135
|
+
export function variableKey(text, sc) {
|
|
136
|
+
const eqName = /^Q\{([^}]*)\}(.+)$/.exec(text);
|
|
137
|
+
if (eqName) return clark(eqName[1], eqName[2]);
|
|
138
|
+
const colon = text.indexOf(":");
|
|
139
|
+
if (colon < 0) return clark("", text);
|
|
140
|
+
const name = { prefix: text.slice(0, colon), local: text.slice(colon + 1) };
|
|
141
|
+
return clark(namespaceOf({ ...name, uri: null }, sc, ""), name.local);
|
|
142
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/` operator: a step applied to each node of the left operand, and
|
|
3
|
+
* the results combined (XPath 3.1 section 3.3.1.1): nodes in document
|
|
4
|
+
* order without duplicates, or atomic values and functions in order.
|
|
5
|
+
*
|
|
6
|
+
* Sorting is skipped where the order is known: a single input node and an
|
|
7
|
+
* axis step, or sorted sibling inputs and an axis that stays inside each
|
|
8
|
+
* input's subtree (child, attribute, namespace, self, descendant,
|
|
9
|
+
* descendant-or-self), whose results are then disjoint and in order.
|
|
10
|
+
*
|
|
11
|
+
* @module @tradik/xslt3/xpath/eval/steps
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { XPathError } from "../../errors.js";
|
|
15
|
+
import { isNode } from "../../xdm/atomic.js";
|
|
16
|
+
import { isContainer, parentOf } from "./domNodes.js";
|
|
17
|
+
import { withFocus } from "./scope.js";
|
|
18
|
+
|
|
19
|
+
const EMPTY = Object.freeze([]);
|
|
20
|
+
|
|
21
|
+
/** Axes that select inside the subtree of the context node. */
|
|
22
|
+
const SUBTREE_AXES = new Set([
|
|
23
|
+
"child",
|
|
24
|
+
"attribute",
|
|
25
|
+
"namespace",
|
|
26
|
+
"self",
|
|
27
|
+
"descendant",
|
|
28
|
+
"descendant-or-self",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
/** Axes that select only children or descendants of the context node. */
|
|
32
|
+
const CHILD_AXES = new Set(["child", "descendant"]);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Combines the results of a step applied to each input item.
|
|
36
|
+
* @param {Array[]} results - One sequence per input item
|
|
37
|
+
* @param {import("./scope.js").Context} ctx
|
|
38
|
+
* @returns {Array} nodes in document order, or the concatenated values
|
|
39
|
+
* @throws {XPathError} XPTY0018 when nodes and other items are mixed
|
|
40
|
+
*/
|
|
41
|
+
function combine(results, ctx) {
|
|
42
|
+
const all = results.flat();
|
|
43
|
+
let nodes = 0;
|
|
44
|
+
for (const item of all) if (isNode(item)) nodes++;
|
|
45
|
+
if (nodes === 0) return all;
|
|
46
|
+
if (nodes !== all.length) {
|
|
47
|
+
throw new XPathError(
|
|
48
|
+
"XPTY0018",
|
|
49
|
+
"A path step returned both nodes and other items",
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return ctx.dyn.order.sort(all);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {Node[]} nodes - At least two nodes
|
|
57
|
+
* @returns {boolean} whether all the nodes have the same parent
|
|
58
|
+
*/
|
|
59
|
+
function areSiblings(nodes) {
|
|
60
|
+
const parent = parentOf(nodes[0]);
|
|
61
|
+
if (parent === null) return false;
|
|
62
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
63
|
+
if (parentOf(nodes[i]) !== parent) return false;
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Evaluator of `E1/E2` for an evaluated E1.
|
|
70
|
+
* @param {import("./scope.js").Evaluator} step - E2
|
|
71
|
+
* @param {string|null} axis - Axis of E2 when it is an axis step
|
|
72
|
+
* @returns {(input: Array, ctx: object, sorted: boolean) => Array} the
|
|
73
|
+
* combined result; `sorted` tells that the input is in document order
|
|
74
|
+
*/
|
|
75
|
+
export function stepper(step, axis) {
|
|
76
|
+
const subtree = SUBTREE_AXES.has(axis);
|
|
77
|
+
const childless = CHILD_AXES.has(axis);
|
|
78
|
+
return (input, ctx, sorted) => {
|
|
79
|
+
const size = input.length;
|
|
80
|
+
if (size === 0) return EMPTY;
|
|
81
|
+
const results = [];
|
|
82
|
+
for (let i = 0; i < size; i++) {
|
|
83
|
+
if (!isNode(input[i])) {
|
|
84
|
+
throw new XPathError(
|
|
85
|
+
"XPTY0019",
|
|
86
|
+
"The left operand of / must contain only nodes",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
// An axis step finds no children nor descendants of a leaf node (a
|
|
90
|
+
// shortcut for the text nodes reached by `//`)
|
|
91
|
+
if (childless && !isContainer(input[i])) continue;
|
|
92
|
+
results.push(step(withFocus(ctx, input[i], i + 1, size)));
|
|
93
|
+
}
|
|
94
|
+
if (size === 1 && axis !== null) return results[0] ?? EMPTY;
|
|
95
|
+
if (sorted && subtree && areSiblings(input)) return results.flat();
|
|
96
|
+
return combine(results, ctx);
|
|
97
|
+
};
|
|
98
|
+
}
|