@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,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compilers of function calls (XPath 3.1 section 3.1.5): static calls,
|
|
3
|
+
* partial application, named function references, dynamic calls and the
|
|
4
|
+
* arrow operator.
|
|
5
|
+
*
|
|
6
|
+
* @module @tradik/xslt3/xpath/eval/calls
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { XPathError } from "../../errors.js";
|
|
10
|
+
import { FunctionItem } from "../../items/function.js";
|
|
11
|
+
import { isFunctionItem } from "../../xdm/atomic.js";
|
|
12
|
+
import { integerItem } from "./atomics.js";
|
|
13
|
+
import { functionItemOf, resolveFunction } from "./functionItems.js";
|
|
14
|
+
import { contextItem } from "./scope.js";
|
|
15
|
+
import { signatureOf } from "./sequenceType.js";
|
|
16
|
+
import { namespaceOf } from "./staticContext.js";
|
|
17
|
+
import { NS } from "../../functions/signatures.js";
|
|
18
|
+
|
|
19
|
+
const isPlaceholder = (arg) => arg.type === "ArgumentPlaceholder";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Partial application: a function item taking the placeholder arguments.
|
|
23
|
+
* @param {Array<Function|null>} args - Evaluators, null for placeholders
|
|
24
|
+
* @param {{params: object[], returns: object}|null} signature - Of the
|
|
25
|
+
* target function, null when unknown
|
|
26
|
+
* @param {(ctx: object) => (args: Array<Array>) => Array} target - Gives
|
|
27
|
+
* the function to call
|
|
28
|
+
* @returns {import("./scope.js").Evaluator}
|
|
29
|
+
*/
|
|
30
|
+
function partial(args, signature, target) {
|
|
31
|
+
const holes = [];
|
|
32
|
+
args.forEach((arg, i) => arg === null && holes.push(i));
|
|
33
|
+
return (ctx) => {
|
|
34
|
+
const { invoke, signature: actual = signature } = target(ctx);
|
|
35
|
+
const fixed = args.map((arg) => (arg === null ? null : arg(ctx)));
|
|
36
|
+
return [
|
|
37
|
+
new FunctionItem({
|
|
38
|
+
arity: holes.length,
|
|
39
|
+
signature: {
|
|
40
|
+
params: holes.map((i) => actual.params[i]),
|
|
41
|
+
returns: actual.returns,
|
|
42
|
+
},
|
|
43
|
+
invoke: (values) => {
|
|
44
|
+
const all = fixed.slice();
|
|
45
|
+
holes.forEach((hole, i) => (all[hole] = values[i]));
|
|
46
|
+
return invoke(all);
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
];
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {Array} sequence - Value of the function expression
|
|
55
|
+
* @param {number} arity - Number of arguments supplied
|
|
56
|
+
* @returns {*} the function item to call
|
|
57
|
+
* @throws {XPathError} XPTY0004 when it is not a single function of that
|
|
58
|
+
* arity
|
|
59
|
+
*/
|
|
60
|
+
function calledFunction(sequence, arity) {
|
|
61
|
+
const [item] = sequence;
|
|
62
|
+
if (sequence.length !== 1 || !isFunctionItem(item)) {
|
|
63
|
+
throw new XPathError(
|
|
64
|
+
"XPTY0004",
|
|
65
|
+
"A dynamic function call needs exactly one function item",
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (item.arity !== arity) {
|
|
69
|
+
throw new XPathError(
|
|
70
|
+
"XPTY0004",
|
|
71
|
+
`The function has arity ${item.arity}, not ${arity}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return item;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Compilers by node type. */
|
|
78
|
+
export const callCompilers = {
|
|
79
|
+
FunctionCall(node, scope, compile) {
|
|
80
|
+
const { sc } = scope;
|
|
81
|
+
const uri = namespaceOf(node.name, sc, sc.defaultFunctionNamespace);
|
|
82
|
+
const local = node.name.local;
|
|
83
|
+
const arity = node.arguments.length;
|
|
84
|
+
const resolved = resolveFunction(uri, local, arity, sc);
|
|
85
|
+
const args = node.arguments.map((arg) =>
|
|
86
|
+
isPlaceholder(arg) ? null : compile(arg, scope),
|
|
87
|
+
);
|
|
88
|
+
if (resolved.signature === null) return () => resolved.call();
|
|
89
|
+
if (args.includes(null)) {
|
|
90
|
+
return partial(args, resolved.signature, (ctx) => ({
|
|
91
|
+
invoke: (values) => resolved.call(values, ctx),
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
if (uri === NS.fn && arity === 0 && resolved.focus) {
|
|
95
|
+
if (local === "position") {
|
|
96
|
+
return (ctx) => (contextItem(ctx), [integerItem(ctx.position)]);
|
|
97
|
+
}
|
|
98
|
+
if (local === "last") {
|
|
99
|
+
return (ctx) => (contextItem(ctx), [integerItem(ctx.size)]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const { call } = resolved;
|
|
103
|
+
if (arity === 1) {
|
|
104
|
+
const [arg] = args;
|
|
105
|
+
return (ctx) => call([arg(ctx)], ctx);
|
|
106
|
+
}
|
|
107
|
+
return (ctx) =>
|
|
108
|
+
call(
|
|
109
|
+
args.map((arg) => arg(ctx)),
|
|
110
|
+
ctx,
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
NamedFunctionRef(node, scope) {
|
|
115
|
+
const { sc } = scope;
|
|
116
|
+
const uri = namespaceOf(node.name, sc, sc.defaultFunctionNamespace);
|
|
117
|
+
const resolved = resolveFunction(uri, node.name.local, node.arity, sc);
|
|
118
|
+
return (ctx) => [functionItemOf(resolved, ctx)];
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
DynamicFunctionCall(node, scope, compile) {
|
|
122
|
+
const target = compile(node.functionExpr, scope);
|
|
123
|
+
const arity = node.arguments.length;
|
|
124
|
+
const args = node.arguments.map((arg) =>
|
|
125
|
+
isPlaceholder(arg) ? null : compile(arg, scope),
|
|
126
|
+
);
|
|
127
|
+
if (args.includes(null)) {
|
|
128
|
+
return partial(args, null, (ctx) => {
|
|
129
|
+
const item = calledFunction(target(ctx), arity);
|
|
130
|
+
return {
|
|
131
|
+
invoke: (values) => item.invoke(values),
|
|
132
|
+
signature: signatureOf(item),
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return (ctx) => {
|
|
137
|
+
const item = calledFunction(target(ctx), arity);
|
|
138
|
+
return item.invoke(args.map((arg) => arg(ctx)));
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
ArrowExpr(node, scope, compile) {
|
|
143
|
+
const args = [node.expr, ...node.arguments];
|
|
144
|
+
const { start, end } = node;
|
|
145
|
+
if (node.functionName) {
|
|
146
|
+
return compile(
|
|
147
|
+
{
|
|
148
|
+
type: "FunctionCall",
|
|
149
|
+
name: node.functionName,
|
|
150
|
+
arguments: args,
|
|
151
|
+
start,
|
|
152
|
+
end,
|
|
153
|
+
},
|
|
154
|
+
scope,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return compile(
|
|
158
|
+
{
|
|
159
|
+
type: "DynamicFunctionCall",
|
|
160
|
+
functionExpr: node.functionExpr,
|
|
161
|
+
arguments: args,
|
|
162
|
+
start,
|
|
163
|
+
end,
|
|
164
|
+
},
|
|
165
|
+
scope,
|
|
166
|
+
);
|
|
167
|
+
},
|
|
168
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The coercion rules (XPath 3.1 section 3.1.5.2, "function conversion
|
|
3
|
+
* rules"): how a value is converted to an expected sequence type when it
|
|
4
|
+
* is passed to a function or returned from one. Atomization, casting of
|
|
5
|
+
* xs:untypedAtomic, numeric and URI promotion, function coercion, then a
|
|
6
|
+
* type check (XPTY0004); in XPath 1.0 compatibility mode the 1.0
|
|
7
|
+
* conversions to string and number come first.
|
|
8
|
+
*
|
|
9
|
+
* @module @tradik/xslt3/xpath/eval/coercion
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { XPathError } from "../../errors.js";
|
|
13
|
+
import { isFunctionItem } from "../../xdm/atomic.js";
|
|
14
|
+
import { cast } from "../../xdm/cast.js";
|
|
15
|
+
import { toNumber } from "../../xdm/generalCompare.js";
|
|
16
|
+
import { atomize, stringValue } from "../../xdm/nodes.js";
|
|
17
|
+
import { derivesFrom, types } from "../../xdm/types.js";
|
|
18
|
+
import { FunctionItem } from "../../items/function.js";
|
|
19
|
+
import { stringItem } from "./atomics.js";
|
|
20
|
+
import { matchesSequenceType } from "./sequenceType.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Promotes or casts one atomic value towards the expected atomic types.
|
|
24
|
+
* @param {import("../../xdm/atomic.js").AtomicValue} value
|
|
25
|
+
* @param {object[]} expected - Atomic type descriptors
|
|
26
|
+
* @returns {import("../../xdm/atomic.js").AtomicValue}
|
|
27
|
+
*/
|
|
28
|
+
function convertAtomic(value, expected) {
|
|
29
|
+
if (expected.some((t) => derivesFrom(value.type, t))) return value;
|
|
30
|
+
if (value.type === types.untypedAtomic) {
|
|
31
|
+
if (
|
|
32
|
+
expected.some((t) => t.primitive === types.QName || t === types.NOTATION)
|
|
33
|
+
) {
|
|
34
|
+
throw new XPathError(
|
|
35
|
+
"XPTY0117",
|
|
36
|
+
"An untyped value cannot be converted to xs:QName or xs:NOTATION",
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
const target = expected.length > 1 ? types.double : expected[0];
|
|
40
|
+
return target.abstract ? value : cast(value, target);
|
|
41
|
+
}
|
|
42
|
+
const primitive = value.type.primitive.localName;
|
|
43
|
+
for (const target of expected) {
|
|
44
|
+
const name = target.localName;
|
|
45
|
+
const numeric =
|
|
46
|
+
(name === "double" &&
|
|
47
|
+
(primitive === "decimal" || primitive === "float")) ||
|
|
48
|
+
(name === "float" && primitive === "decimal");
|
|
49
|
+
if (numeric || (name === "string" && primitive === "anyURI")) {
|
|
50
|
+
return cast(value, target);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* XPath 1.0 compatibility mode conversions of an argument: only the
|
|
58
|
+
* first item is kept for a single expected item, converted with fn:string
|
|
59
|
+
* for xs:string and fn:number for xs:double.
|
|
60
|
+
* @param {Array} sequence
|
|
61
|
+
* @param {object|null} itemType - Expected item type
|
|
62
|
+
* @param {string} occurrence
|
|
63
|
+
* @returns {Array}
|
|
64
|
+
*/
|
|
65
|
+
function compatible(sequence, itemType, occurrence) {
|
|
66
|
+
if (occurrence === "*" || occurrence === "+") return sequence;
|
|
67
|
+
const first = sequence.slice(0, 1);
|
|
68
|
+
const expected = itemType?.kind === "atomic" ? itemType.types[0] : null;
|
|
69
|
+
if (expected === types.string) {
|
|
70
|
+
return [stringItem(first.length ? stringValue(first[0]) : "")];
|
|
71
|
+
}
|
|
72
|
+
if (expected === types.double) {
|
|
73
|
+
const value = atomize(first)[0];
|
|
74
|
+
return [value ? toNumber(value) : toNumber(stringItem("NaN"))];
|
|
75
|
+
}
|
|
76
|
+
return first;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Function coercion: wraps a function item so that it has the signature
|
|
81
|
+
* of a function test.
|
|
82
|
+
* @param {*} item
|
|
83
|
+
* @param {object} test - Compiled TypedFunctionTest
|
|
84
|
+
* @param {string} what - Description for error messages
|
|
85
|
+
* @returns {FunctionItem}
|
|
86
|
+
*/
|
|
87
|
+
function coerceFunction(item, test, what) {
|
|
88
|
+
if (item.arity !== test.params.length) {
|
|
89
|
+
throw new XPathError(
|
|
90
|
+
"XPTY0004",
|
|
91
|
+
`The ${what} must be a function of arity ${test.params.length}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return new FunctionItem({
|
|
95
|
+
name: item.name,
|
|
96
|
+
arity: item.arity,
|
|
97
|
+
signature: { params: test.params, returns: test.returns },
|
|
98
|
+
invoke: (args) =>
|
|
99
|
+
coerce(
|
|
100
|
+
item.invoke(
|
|
101
|
+
args.map((arg, i) =>
|
|
102
|
+
coerce(arg, test.params[i], {
|
|
103
|
+
what: `argument ${i + 1} of ${what}`,
|
|
104
|
+
}),
|
|
105
|
+
),
|
|
106
|
+
),
|
|
107
|
+
test.returns,
|
|
108
|
+
{ what: `result of ${what}` },
|
|
109
|
+
),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Converts a value to an expected sequence type.
|
|
115
|
+
* @param {Array} sequence
|
|
116
|
+
* @param {import("./sequenceType.js").SequenceType} type
|
|
117
|
+
* @param {{what: string, compatible?: boolean}} options - `what` names
|
|
118
|
+
* the value in error messages; `compatible` applies the XPath 1.0 rules
|
|
119
|
+
* @returns {Array} the converted value
|
|
120
|
+
* @throws {XPathError} XPTY0004 when the value does not match the type,
|
|
121
|
+
* cast errors (FORG0001...) for untyped values
|
|
122
|
+
*/
|
|
123
|
+
export function coerce(sequence, type, options) {
|
|
124
|
+
const itemType = type.itemType;
|
|
125
|
+
if (itemType?.kind === "item" && type.occurrence === "*") return sequence;
|
|
126
|
+
let value = sequence;
|
|
127
|
+
if (options.compatible) {
|
|
128
|
+
value = compatible(value, itemType, type.occurrence);
|
|
129
|
+
}
|
|
130
|
+
if (itemType?.kind === "atomic") {
|
|
131
|
+
value = atomize(value).map((v) => convertAtomic(v, itemType.types));
|
|
132
|
+
} else if (itemType?.kind === "function") {
|
|
133
|
+
value = value.map((item) =>
|
|
134
|
+
isFunctionItem(item) && !itemType.matches(item)
|
|
135
|
+
? coerceFunction(item, itemType, options.what)
|
|
136
|
+
: item,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
if (!matchesSequenceType(value, type)) {
|
|
140
|
+
throw new XPathError(
|
|
141
|
+
"XPTY0004",
|
|
142
|
+
`The ${options.what} does not match the required type`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The compiler: static analysis and translation of an AST into nested
|
|
3
|
+
* closures, in one pass. Names are resolved (XPST0081), variables
|
|
4
|
+
* (XPST0008), functions (XPST0017) and types (XPST0051, XPST0080) checked,
|
|
5
|
+
* and each node becomes an evaluator `(ctx) => Item[]`, so evaluation
|
|
6
|
+
* never walks the AST again.
|
|
7
|
+
*
|
|
8
|
+
* @module @tradik/xslt3/xpath/eval/compiler
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { XPathError } from "../../errors.js";
|
|
12
|
+
import { callCompilers } from "./calls.js";
|
|
13
|
+
import { constructorCompilers } from "./constructors.js";
|
|
14
|
+
import { flowCompilers } from "./flow.js";
|
|
15
|
+
import { inlineCompilers } from "./inline.js";
|
|
16
|
+
import { operatorCompilers } from "./operators.js";
|
|
17
|
+
import { pathCompilers } from "./paths.js";
|
|
18
|
+
import { primaryCompilers } from "./primary.js";
|
|
19
|
+
import { typeCompilers } from "./typeExprs.js";
|
|
20
|
+
|
|
21
|
+
/** Compilers by AST node type. */
|
|
22
|
+
const compilers = {
|
|
23
|
+
...primaryCompilers,
|
|
24
|
+
...flowCompilers,
|
|
25
|
+
...operatorCompilers,
|
|
26
|
+
...typeCompilers,
|
|
27
|
+
...pathCompilers,
|
|
28
|
+
...callCompilers,
|
|
29
|
+
...inlineCompilers,
|
|
30
|
+
...constructorCompilers,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Compiles an expression node.
|
|
35
|
+
* @param {import("../syntax/ast.js").Expr} node
|
|
36
|
+
* @param {import("./scope.js").Scope} scope
|
|
37
|
+
* @returns {import("./scope.js").Evaluator}
|
|
38
|
+
* @throws {XPathError} the static errors of the expression
|
|
39
|
+
*/
|
|
40
|
+
export function compileNode(node, scope) {
|
|
41
|
+
const compiler = compilers[node.type];
|
|
42
|
+
if (!compiler) {
|
|
43
|
+
throw new XPathError("XPST0003", `Unsupported expression ${node.type}`);
|
|
44
|
+
}
|
|
45
|
+
return compiler(node, scope, compileNode);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Runs a function, turning the RangeErrors of JavaScript limits (stack
|
|
50
|
+
* depth of deeply nested expressions or recursive functions, string,
|
|
51
|
+
* array and BigInt sizes) into XPDY0130, the error for implementation
|
|
52
|
+
* limits.
|
|
53
|
+
* @template T
|
|
54
|
+
* @param {() => T} run
|
|
55
|
+
* @returns {T}
|
|
56
|
+
* @throws {XPathError} XPDY0130 instead of a RangeError
|
|
57
|
+
*/
|
|
58
|
+
export function withinLimits(run) {
|
|
59
|
+
try {
|
|
60
|
+
return run();
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error instanceof RangeError) {
|
|
63
|
+
throw new XPathError(
|
|
64
|
+
"XPDY0130",
|
|
65
|
+
`Implementation limit exceeded: ${error.message}`,
|
|
66
|
+
{ cause: error },
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compilers of map and array constructors and of the lookup operator `?`
|
|
3
|
+
* (XPath 3.1 sections 3.11.1 to 3.11.3).
|
|
4
|
+
*
|
|
5
|
+
* @module @tradik/xslt3/xpath/eval/constructors
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { XPathError } from "../../errors.js";
|
|
9
|
+
import { XdmArray } from "../../items/array.js";
|
|
10
|
+
import { XdmMap } from "../../items/map.js";
|
|
11
|
+
import { AtomicValue, isArray, isMap } from "../../xdm/atomic.js";
|
|
12
|
+
import { atomize } from "../../xdm/nodes.js";
|
|
13
|
+
import { types } from "../../xdm/types.js";
|
|
14
|
+
import { contextItem } from "./scope.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Compiles the key specifier of a lookup.
|
|
18
|
+
* @param {import("../syntax/ast.js").Lookup} node
|
|
19
|
+
* @param {import("./scope.js").Scope} scope
|
|
20
|
+
* @param {Function} compile
|
|
21
|
+
* @returns {((ctx: object) => import("../../xdm/atomic.js").AtomicValue[])|null}
|
|
22
|
+
* the keys, null for the wildcard
|
|
23
|
+
*/
|
|
24
|
+
function compileKeys(node, scope, compile) {
|
|
25
|
+
switch (node.keyKind) {
|
|
26
|
+
case "wildcard":
|
|
27
|
+
return null;
|
|
28
|
+
case "name": {
|
|
29
|
+
const keys = [new AtomicValue(types.string, node.key)];
|
|
30
|
+
return () => keys;
|
|
31
|
+
}
|
|
32
|
+
case "integer": {
|
|
33
|
+
const keys = [new AtomicValue(types.integer, BigInt(node.key))];
|
|
34
|
+
return () => keys;
|
|
35
|
+
}
|
|
36
|
+
default: {
|
|
37
|
+
const expr = compile(node.key, scope);
|
|
38
|
+
return (ctx) => atomize(expr(ctx));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Looks keys up in one map or array.
|
|
45
|
+
* @param {*} item
|
|
46
|
+
* @param {import("../../xdm/atomic.js").AtomicValue[]|null} keys - null for
|
|
47
|
+
* all entries or members
|
|
48
|
+
* @param {Array} result - Values are appended
|
|
49
|
+
* @throws {XPathError} XPTY0004 for an item that is neither a map nor an
|
|
50
|
+
* array, or a non-integer array key; FOAY0001 for a position out of
|
|
51
|
+
* bounds
|
|
52
|
+
*/
|
|
53
|
+
function lookup(item, keys, result) {
|
|
54
|
+
if (isMap(item)) {
|
|
55
|
+
if (keys === null) {
|
|
56
|
+
for (const { value } of item.entries.values()) result.push(...value);
|
|
57
|
+
} else {
|
|
58
|
+
for (const key of keys) result.push(...(item.get(key) ?? []));
|
|
59
|
+
}
|
|
60
|
+
} else if (isArray(item)) {
|
|
61
|
+
if (keys === null) {
|
|
62
|
+
for (const member of item.members) result.push(...member);
|
|
63
|
+
} else {
|
|
64
|
+
for (const key of keys) result.push(...item.invoke([[key]]));
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
throw new XPathError(
|
|
68
|
+
"XPTY0004",
|
|
69
|
+
"The lookup operator applies to maps and arrays only",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Compilers by node type. */
|
|
75
|
+
export const constructorCompilers = {
|
|
76
|
+
MapConstructor(node, scope, compile) {
|
|
77
|
+
const entries = node.entries.map((entry) => ({
|
|
78
|
+
key: compile(entry.key, scope),
|
|
79
|
+
value: compile(entry.value, scope),
|
|
80
|
+
}));
|
|
81
|
+
return (ctx) => {
|
|
82
|
+
const pairs = entries.map(({ key, value }) => {
|
|
83
|
+
const keys = atomize(key(ctx));
|
|
84
|
+
if (keys.length !== 1) {
|
|
85
|
+
throw new XPathError(
|
|
86
|
+
"XPTY0004",
|
|
87
|
+
"A map key must be a single atomic value",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return [keys[0], value(ctx)];
|
|
91
|
+
});
|
|
92
|
+
const duplicate = () => {
|
|
93
|
+
throw new XPathError("XQDY0137", "Duplicate key in a map constructor");
|
|
94
|
+
};
|
|
95
|
+
return [XdmMap.from(pairs, duplicate)];
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
SquareArrayConstructor(node, scope, compile) {
|
|
100
|
+
const members = node.members.map((member) => compile(member, scope));
|
|
101
|
+
return (ctx) => [new XdmArray(members.map((member) => member(ctx)))];
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
CurlyArrayConstructor(node, scope, compile) {
|
|
105
|
+
const expr = compile(node.expr, scope);
|
|
106
|
+
return (ctx) => [new XdmArray(expr(ctx).map((item) => [item]))];
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
Lookup(node, scope, compile) {
|
|
110
|
+
const base = compile(node.base, scope);
|
|
111
|
+
const keys = compileKeys(node, scope, compile);
|
|
112
|
+
return (ctx) => {
|
|
113
|
+
const items = base(ctx);
|
|
114
|
+
const keyValues = keys && keys(ctx);
|
|
115
|
+
const result = [];
|
|
116
|
+
for (const item of items) lookup(item, keyValues, result);
|
|
117
|
+
return result;
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
UnaryLookup(node, scope, compile) {
|
|
122
|
+
const keys = compileKeys(node, scope, compile);
|
|
123
|
+
return (ctx) => {
|
|
124
|
+
const result = [];
|
|
125
|
+
lookup(contextItem(ctx), keys && keys(ctx), result);
|
|
126
|
+
return result;
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decimal formats of the static context (XPath 3.1 section 2.1.1), used by
|
|
3
|
+
* fn:format-number. Formats are given by name: a lexical QName or an EQName
|
|
4
|
+
* (prefixes resolved with the static namespaces), "" for the default one.
|
|
5
|
+
*
|
|
6
|
+
* Properties are read in camelCase (`decimalSeparator`) or as the
|
|
7
|
+
* attribute names of xsl:decimal-format (`decimal-separator`, `NaN`).
|
|
8
|
+
*
|
|
9
|
+
* @module @tradik/xslt3/xpath/eval/decimalFormats
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Property names of a decimal format, by attribute name. */
|
|
13
|
+
const PROPERTIES = {
|
|
14
|
+
"decimal-separator": "decimalSeparator",
|
|
15
|
+
"grouping-separator": "groupingSeparator",
|
|
16
|
+
"exponent-separator": "exponentSeparator",
|
|
17
|
+
infinity: "infinity",
|
|
18
|
+
"minus-sign": "minusSign",
|
|
19
|
+
NaN: "nan",
|
|
20
|
+
nan: "nan",
|
|
21
|
+
percent: "percent",
|
|
22
|
+
"per-mille": "perMille",
|
|
23
|
+
"zero-digit": "zeroDigit",
|
|
24
|
+
digit: "digit",
|
|
25
|
+
"pattern-separator": "patternSeparator",
|
|
26
|
+
};
|
|
27
|
+
const CAMEL = new Set(Object.values(PROPERTIES));
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The properties of a format definition.
|
|
31
|
+
* @param {object} definition
|
|
32
|
+
* @returns {object} camelCase properties
|
|
33
|
+
*/
|
|
34
|
+
function propertiesOf(definition) {
|
|
35
|
+
const properties = {};
|
|
36
|
+
for (const [key, value] of Object.entries(definition)) {
|
|
37
|
+
const name = PROPERTIES[key] ?? (CAMEL.has(key) ? key : null);
|
|
38
|
+
if (name) properties[name] = value;
|
|
39
|
+
}
|
|
40
|
+
return properties;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Expanded name of a format definition: a prefix declared on the
|
|
45
|
+
* definition itself (an `xmlns:p` property, as xsl:decimal-format
|
|
46
|
+
* elements have) comes first, then the static namespaces.
|
|
47
|
+
* @param {string} name
|
|
48
|
+
* @param {object} definition
|
|
49
|
+
* @param {(name: string) => string} expand
|
|
50
|
+
* @returns {string|null} Clark name, "" for the default format, null when
|
|
51
|
+
* the prefix is not declared
|
|
52
|
+
*/
|
|
53
|
+
function definitionKey(name, definition, expand) {
|
|
54
|
+
if (name === "") return "";
|
|
55
|
+
const colon = name.indexOf(":");
|
|
56
|
+
const own =
|
|
57
|
+
colon > 0 && !name.startsWith("Q{")
|
|
58
|
+
? definition[`xmlns:${name.slice(0, colon)}`]
|
|
59
|
+
: undefined;
|
|
60
|
+
if (own !== undefined) return `{${own}}${name.slice(colon + 1)}`;
|
|
61
|
+
try {
|
|
62
|
+
return expand(name);
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Builds the decimal formats of a static context.
|
|
70
|
+
* @param {Array<object>|object|Map|undefined} formats - An array of
|
|
71
|
+
* definitions with a `name` property (absent for the default format),
|
|
72
|
+
* or definitions by name
|
|
73
|
+
* @param {(name: string) => string} expand - Expands a name as written
|
|
74
|
+
* to a Clark name (throws for an undeclared prefix)
|
|
75
|
+
* @returns {{get: (name: string) => object|undefined}} the formats by
|
|
76
|
+
* name (surrounding whitespace ignored); get() returns undefined for an
|
|
77
|
+
* unknown name or prefix
|
|
78
|
+
*/
|
|
79
|
+
export function createDecimalFormats(formats, expand) {
|
|
80
|
+
const entries = Array.isArray(formats)
|
|
81
|
+
? formats.map((format) => [format.name ?? "", format])
|
|
82
|
+
: formats instanceof Map
|
|
83
|
+
? [...formats]
|
|
84
|
+
: Object.entries(formats ?? {});
|
|
85
|
+
const byName = new Map();
|
|
86
|
+
for (const [name, format] of entries) {
|
|
87
|
+
const key = definitionKey(name.trim(), format, expand);
|
|
88
|
+
if (key !== null) byName.set(key, propertiesOf(format));
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
get: (name) => byName.get(definitionKey(name.trim(), {}, expand)),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Axis results remembered for the rest of an evaluation, so that
|
|
3
|
+
* `for $c in ... return //item[@cat = $c]` walks the tree and looks the
|
|
4
|
+
* attributes up once instead of once per iteration:
|
|
5
|
+
*
|
|
6
|
+
* - the descendant axes of a document node, always;
|
|
7
|
+
* - the attributes looked up by name, once the evaluation has walked a
|
|
8
|
+
* tree a second time (a remembered descendant axis was used): before
|
|
9
|
+
* that, in one pass over the tree, remembering costs more than it saves.
|
|
10
|
+
*
|
|
11
|
+
* Only an evaluation that owns its dynamic context turns it on
|
|
12
|
+
* (compileXPath's evaluate sets `dyn.descendantMemo`): an XPath evaluation
|
|
13
|
+
* does not change existing trees (the nodes that functions build are new,
|
|
14
|
+
* complete trees), so a remembered result stays exact while it runs. A
|
|
15
|
+
* caller that shares a dynamic context over trees it changes leaves it
|
|
16
|
+
* off.
|
|
17
|
+
*
|
|
18
|
+
* @module @tradik/xslt3/xpath/eval/descendantMemo
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { axes } from "./axes.js";
|
|
22
|
+
|
|
23
|
+
/** Axes worth remembering: whole subtrees. */
|
|
24
|
+
const MEMO_AXES = new Set(["descendant", "descendant-or-self"]);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} DescendantMemo
|
|
28
|
+
* @property {Map<string, Map<Function, Map<Node, Node[]>>>|null} results -
|
|
29
|
+
* By axis, node test and context node
|
|
30
|
+
* @property {boolean} repeated - Whether a remembered descendant axis was
|
|
31
|
+
* used again
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** @returns {DescendantMemo} the memo of an evaluation */
|
|
35
|
+
export const createDescendantMemo = () => ({ results: null, repeated: false });
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Whether the result of an axis is remembered.
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
*/
|
|
41
|
+
function isRemembered(memo, name, node, test, limit) {
|
|
42
|
+
if (name === "attribute") {
|
|
43
|
+
return memo.repeated && test.qname !== undefined && limit >= 1;
|
|
44
|
+
}
|
|
45
|
+
const type = node.nodeType;
|
|
46
|
+
return (
|
|
47
|
+
limit === Infinity && MEMO_AXES.has(name) && (type === 9 || type === 11)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The nodes of an axis, from the memo of the evaluation when there is one
|
|
53
|
+
* and the axis is remembered.
|
|
54
|
+
* @param {string} name - Axis name
|
|
55
|
+
* @param {Node} node - Context node
|
|
56
|
+
* @param {(node: Node) => boolean} test - Node test (one function per
|
|
57
|
+
* compiled step, so it identifies the step)
|
|
58
|
+
* @param {number} limit - Most nodes to collect
|
|
59
|
+
* @param {object} dyn - Dynamic context
|
|
60
|
+
* @returns {Node[]} the nodes in axis order (a new array)
|
|
61
|
+
*/
|
|
62
|
+
export function axisNodes(name, node, test, limit, dyn) {
|
|
63
|
+
const memo = dyn.descendantMemo;
|
|
64
|
+
if (!memo || !isRemembered(memo, name, node, test, limit)) {
|
|
65
|
+
return axes[name](node, test, limit);
|
|
66
|
+
}
|
|
67
|
+
// Created on first use: most evaluations never need it
|
|
68
|
+
memo.results ??= new Map();
|
|
69
|
+
let byTest = memo.results.get(name);
|
|
70
|
+
if (!byTest) memo.results.set(name, (byTest = new Map()));
|
|
71
|
+
let byNode = byTest.get(test);
|
|
72
|
+
if (!byNode) byTest.set(test, (byNode = new Map()));
|
|
73
|
+
let nodes = byNode.get(node);
|
|
74
|
+
if (nodes) memo.repeated = true;
|
|
75
|
+
else byNode.set(node, (nodes = axes[name](node, test, limit)));
|
|
76
|
+
return nodes.slice();
|
|
77
|
+
}
|