@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,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XPath 3.1: compile an expression once, evaluate it many times.
|
|
3
|
+
*
|
|
4
|
+
* ```js
|
|
5
|
+
* import { compileXPath, evaluateXPath } from "@tradik/xslt3/xpath";
|
|
6
|
+
* const expr = compileXPath("//item[@price > $min]", { variables: ["min"] });
|
|
7
|
+
* expr.evaluate(document, { variables: { min: 10 } }); // [Element, ...]
|
|
8
|
+
* evaluateXPath("sum(1 to 10)", null); // [AtomicValue xs:integer 55]
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* Results are arrays of XDM items: DOM nodes, AtomicValue (xdm/atomic.js),
|
|
12
|
+
* XdmMap, XdmArray and FunctionItem (items/). JavaScript values given as
|
|
13
|
+
* variables or context item are converted (see eval/values.js).
|
|
14
|
+
*
|
|
15
|
+
* @module @tradik/xslt3/xpath
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { XPathError } from "../errors.js";
|
|
19
|
+
import { coreFunctions } from "../functions/core.js";
|
|
20
|
+
import {
|
|
21
|
+
createFunctionLibrary,
|
|
22
|
+
FunctionLibrary,
|
|
23
|
+
} from "../functions/registry.js";
|
|
24
|
+
import { compileNode, withinLimits } from "./eval/compiler.js";
|
|
25
|
+
import { createDescendantMemo } from "./eval/descendantMemo.js";
|
|
26
|
+
import { createDynamicContext } from "./eval/dynamicContext.js";
|
|
27
|
+
import { rootScope } from "./eval/scope.js";
|
|
28
|
+
import { createStaticContext, variableKey } from "./eval/staticContext.js";
|
|
29
|
+
import { toSequence } from "./eval/values.js";
|
|
30
|
+
import { parseXPath } from "./syntax/index.js";
|
|
31
|
+
|
|
32
|
+
/** The library of the built-in functions. */
|
|
33
|
+
export const defaultFunctionLibrary = createFunctionLibrary(coreFunctions);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {object} StaticOptions
|
|
37
|
+
* @property {object|Map|Array<{prefix: string, uri: string}>} [namespaces]
|
|
38
|
+
* - Prefix bindings added to the predeclared ones (xml, xs, xsi, fn,
|
|
39
|
+
* map, array, math, err); the prefix "" sets the default element namespace
|
|
40
|
+
* @property {string} [defaultElementNamespace] - Default element/type namespace
|
|
41
|
+
* @property {string} [defaultFunctionNamespace] - Default: the fn namespace
|
|
42
|
+
* @property {string[]|object} [variables] - Names of the external
|
|
43
|
+
* variables (`name`, `prefix:name` or `Q{uri}name`), or an object whose
|
|
44
|
+
* keys are the names
|
|
45
|
+
* @property {Array|FunctionLibrary} [functions] - Function definitions or
|
|
46
|
+
* modules (arrays of definitions) added to the built-in ones, or a
|
|
47
|
+
* complete library
|
|
48
|
+
* @property {boolean} [backwardsCompatible] - XPath 1.0 compatibility mode
|
|
49
|
+
* @property {string} [baseUri] - Static base URI
|
|
50
|
+
* @property {string} [defaultCollation] - Only the codepoint collation
|
|
51
|
+
* @property {Array<object>|object|Map} [decimalFormats] - Decimal formats
|
|
52
|
+
* for fn:format-number: an array of definitions with a `name` (none for
|
|
53
|
+
* the default format), or definitions by name; properties in camelCase
|
|
54
|
+
* or as xsl:decimal-format attribute names (see eval/decimalFormats.js)
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @typedef {object} DynamicOptions
|
|
59
|
+
* @property {object} [variables] - Values of the external variables by name
|
|
60
|
+
* @property {number} [implicitTimezone] - Minutes, default 0 (UTC)
|
|
61
|
+
* @property {Date|import("../xdm/datetime.js").DateTimeValue} [currentDateTime]
|
|
62
|
+
* @property {(uri: string) => Node} [documentLoader] - Loads fn:doc URIs
|
|
63
|
+
* (absolute, resolved against the base URI)
|
|
64
|
+
* @property {(uri: string) => (string|Uint8Array|ArrayBuffer|{content: string|Uint8Array|ArrayBuffer, encoding?: string, mediaType?: string})} [textLoader]
|
|
65
|
+
* - Loads the text resources of fn:unparsed-text, fn:unparsed-text-lines,
|
|
66
|
+
* fn:unparsed-text-available and fn:json-doc (absolute URIs, resolved
|
|
67
|
+
* against the base URI); bytes are decoded by BOM, `encoding` (external
|
|
68
|
+
* information), the XML declaration of an XML `mediaType`, the
|
|
69
|
+
* function's encoding argument, else UTF-8. Return null or throw when
|
|
70
|
+
* there is no resource (FOUT1170). Default: no resources at all;
|
|
71
|
+
* `readFileUri` (exported) reads `file:` URIs in Node.js, for trusted
|
|
72
|
+
* expressions only
|
|
73
|
+
* @property {(text: string, baseUri?: string) => Document} [xmlParser]
|
|
74
|
+
* - Parses the strings of fn:parse-xml and fn:parse-xml-fragment; throws
|
|
75
|
+
* when the text is not well-formed. Default: `globalThis.DOMParser`,
|
|
76
|
+
* FODC0006 without it
|
|
77
|
+
* @property {(uri: string|null) => Array|null} [collections] - The items
|
|
78
|
+
* of fn:collection by absolute URI (null: the default collection);
|
|
79
|
+
* fn:uri-collection gives their document URIs (xs:anyURI items are
|
|
80
|
+
* returned as they are). Null for an unknown collection (FODC0002).
|
|
81
|
+
* Default: no collections
|
|
82
|
+
* @property {(value: Array, label: string) => void} [trace] - fn:trace output
|
|
83
|
+
* @property {() => Document} [createDocument] - Creates the empty
|
|
84
|
+
* documents in which functions build new nodes (default: from the DOM of
|
|
85
|
+
* the context item, else of the global document)
|
|
86
|
+
* @property {import("./eval/documentOrder.js").DocumentOrder} [documentOrder]
|
|
87
|
+
* - Document order cache to share between evaluations over unchanged trees
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {StaticOptions["functions"]} functions
|
|
92
|
+
* @returns {FunctionLibrary}
|
|
93
|
+
*/
|
|
94
|
+
function libraryOf(functions) {
|
|
95
|
+
if (functions instanceof FunctionLibrary) return functions;
|
|
96
|
+
if (!functions || functions.length === 0) return defaultFunctionLibrary;
|
|
97
|
+
return defaultFunctionLibrary.extend(...functions);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {string[]|object|undefined} variables
|
|
102
|
+
* @returns {string[]} the variable names
|
|
103
|
+
*/
|
|
104
|
+
const variableNames = (variables) =>
|
|
105
|
+
Array.isArray(variables) ? variables : Object.keys(variables ?? {});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Compiles an XPath 3.1 expression.
|
|
109
|
+
* @param {string} expression
|
|
110
|
+
* @param {StaticOptions} [options]
|
|
111
|
+
* @returns {{evaluate: (contextItem?: *, options?: DynamicOptions) => Array}}
|
|
112
|
+
* the compiled expression; `evaluate` takes the context item (undefined
|
|
113
|
+
* for none) and returns the result sequence
|
|
114
|
+
* @throws {XPathError} static errors: XPST0003 (syntax), XPST0008,
|
|
115
|
+
* XPST0017, XPST0051, XPST0080, XPST0081...; XPDY0130 when the
|
|
116
|
+
* expression is nested too deeply
|
|
117
|
+
*/
|
|
118
|
+
export function compileXPath(expression, options = {}) {
|
|
119
|
+
const sc = createStaticContext(options, libraryOf(options.functions));
|
|
120
|
+
const keys = variableNames(options.variables).map((name) =>
|
|
121
|
+
variableKey(name, sc),
|
|
122
|
+
);
|
|
123
|
+
const evaluator = withinLimits(() =>
|
|
124
|
+
compileNode(parseXPath(expression), rootScope(sc, keys)),
|
|
125
|
+
);
|
|
126
|
+
return {
|
|
127
|
+
evaluate(contextItem, dynamicOptions = {}) {
|
|
128
|
+
const values = new Map();
|
|
129
|
+
for (const [name, value] of Object.entries(
|
|
130
|
+
dynamicOptions.variables ?? {},
|
|
131
|
+
)) {
|
|
132
|
+
values.set(variableKey(name, sc), toSequence(value));
|
|
133
|
+
}
|
|
134
|
+
let env = null;
|
|
135
|
+
for (const key of keys) env = { value: values.get(key), next: env };
|
|
136
|
+
const focus = toSequence(contextItem);
|
|
137
|
+
if (focus.length > 1) {
|
|
138
|
+
throw new XPathError("XPTY0004", "The context item must be one item");
|
|
139
|
+
}
|
|
140
|
+
const ctx = {
|
|
141
|
+
item: focus[0],
|
|
142
|
+
position: 1,
|
|
143
|
+
size: 1,
|
|
144
|
+
env,
|
|
145
|
+
dyn: createDynamicContext(sc, dynamicOptions, focus[0]),
|
|
146
|
+
};
|
|
147
|
+
ctx.dyn.descendantMemo = createDescendantMemo();
|
|
148
|
+
return withinLimits(() => evaluator(ctx));
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Static options other than `variables`; any of them disables the cache. */
|
|
154
|
+
const STATIC_OPTIONS = [
|
|
155
|
+
"namespaces",
|
|
156
|
+
"defaultElementNamespace",
|
|
157
|
+
"defaultFunctionNamespace",
|
|
158
|
+
"functions",
|
|
159
|
+
"backwardsCompatible",
|
|
160
|
+
"baseUri",
|
|
161
|
+
"defaultCollation",
|
|
162
|
+
"decimalFormats",
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
/** Most compiled expressions kept by evaluateXPath. */
|
|
166
|
+
export const COMPILED_CACHE_SIZE = 64;
|
|
167
|
+
|
|
168
|
+
/** Compiled expressions of evaluateXPath by key, least recently used first. */
|
|
169
|
+
const compiledCache = new Map();
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The compiled expression of evaluateXPath, from a small LRU cache when
|
|
173
|
+
* the only static option is `variables` (the names are part of the key):
|
|
174
|
+
* other static options can be objects that change between calls, so their
|
|
175
|
+
* expressions are compiled every time.
|
|
176
|
+
* @param {string} expression
|
|
177
|
+
* @param {StaticOptions} options
|
|
178
|
+
* @returns {ReturnType<typeof compileXPath>}
|
|
179
|
+
*/
|
|
180
|
+
function compiledFor(expression, options) {
|
|
181
|
+
const names = variableNames(options.variables);
|
|
182
|
+
const compile = () =>
|
|
183
|
+
compileXPath(expression, { ...options, variables: names });
|
|
184
|
+
if (STATIC_OPTIONS.some((name) => options[name] !== undefined)) {
|
|
185
|
+
return compile();
|
|
186
|
+
}
|
|
187
|
+
const key = JSON.stringify([expression, names]);
|
|
188
|
+
let compiled = compiledCache.get(key);
|
|
189
|
+
if (compiled) compiledCache.delete(key);
|
|
190
|
+
else {
|
|
191
|
+
compiled = compile();
|
|
192
|
+
if (compiledCache.size >= COMPILED_CACHE_SIZE) {
|
|
193
|
+
compiledCache.delete(compiledCache.keys().next().value);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
compiledCache.set(key, compiled);
|
|
197
|
+
return compiled;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Compiles and evaluates an expression; the names of `options.variables`
|
|
202
|
+
* are the external variables. Without other static options, the compiled
|
|
203
|
+
* expression is kept in a small LRU cache (COMPILED_CACHE_SIZE
|
|
204
|
+
* expressions) and reused by the next calls with the same text and
|
|
205
|
+
* variable names; for repeated evaluations, compileXPath once is still
|
|
206
|
+
* the faster way.
|
|
207
|
+
* @param {string} expression
|
|
208
|
+
* @param {*} [contextItem] - Context item, undefined or null for none
|
|
209
|
+
* @param {StaticOptions & DynamicOptions} [options]
|
|
210
|
+
* @returns {Array} the result sequence
|
|
211
|
+
*/
|
|
212
|
+
export function evaluateXPath(expression, contextItem, options = {}) {
|
|
213
|
+
return compiledFor(expression, options).evaluate(contextItem, options);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export { createFunctionLibrary, FunctionLibrary, XPathError };
|
|
217
|
+
export { readFileUri } from "./eval/resources.js";
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The XPath 3.1 abstract syntax tree: expression nodes. Node tests and
|
|
3
|
+
* sequence types are described in typeAst.js.
|
|
4
|
+
*
|
|
5
|
+
* Every node is a plain object `{ type, ...fields, start, end }`, where
|
|
6
|
+
* `start` and `end` are offsets (UTF-16 code units) into the expression.
|
|
7
|
+
* Parentheses leave no node: `(E)` is the node of E, `()` an EmptySequence.
|
|
8
|
+
* Names stay as written ({@link QName}); the evaluator resolves prefixes and
|
|
9
|
+
* default namespaces. Numeric literals keep their lexical form, so decimals
|
|
10
|
+
* lose no precision before the data model sees them.
|
|
11
|
+
*
|
|
12
|
+
* @module @tradik/xslt3/xpath/syntax/ast
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A name as written: `local`, `prefix:local` or `Q{uri}local`.
|
|
17
|
+
*
|
|
18
|
+
* @typedef {object} QName
|
|
19
|
+
* @property {string|null} prefix - Prefix, null when absent
|
|
20
|
+
* @property {string} local - Local part
|
|
21
|
+
* @property {string|null} uri - Namespace of an EQName `Q{uri}local`, else null
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Any expression node.
|
|
26
|
+
*
|
|
27
|
+
* @typedef {SequenceExpr|EmptySequence|ForExpr|LetExpr|QuantifiedExpr|IfExpr|
|
|
28
|
+
* LogicalExpr|ComparisonExpr|StringConcatExpr|RangeExpr|ArithmeticExpr|
|
|
29
|
+
* SetExpr|InstanceOfExpr|TreatExpr|CastableExpr|CastExpr|ArrowExpr|
|
|
30
|
+
* UnaryExpr|SimpleMapExpr|PathExpr|AxisStep|FilterExpr|DynamicFunctionCall|
|
|
31
|
+
* Lookup|UnaryLookup|NumericLiteral|StringLiteral|VarRef|ContextItemExpr|
|
|
32
|
+
* FunctionCall|NamedFunctionRef|InlineFunctionExpr|MapConstructor|
|
|
33
|
+
* SquareArrayConstructor|CurlyArrayConstructor} Expr
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** @typedef {{type: "SequenceExpr", items: Expr[], start: number, end: number}} SequenceExpr `E1, E2, ...` (two or more items) */
|
|
37
|
+
/** @typedef {{type: "EmptySequence", start: number, end: number}} EmptySequence `()` */
|
|
38
|
+
/** @typedef {{type: "Binding", name: QName, expr: Expr, start: number, end: number}} Binding `$name in E` or `$name := E` */
|
|
39
|
+
/** @typedef {{type: "ForExpr", bindings: Binding[], returnExpr: Expr, start: number, end: number}} ForExpr `for $a in A, $b in B return R` */
|
|
40
|
+
/** @typedef {{type: "LetExpr", bindings: Binding[], returnExpr: Expr, start: number, end: number}} LetExpr `let $a := A, $b := B return R` */
|
|
41
|
+
/** @typedef {{type: "QuantifiedExpr", quantifier: "some"|"every", bindings: Binding[], satisfies: Expr, start: number, end: number}} QuantifiedExpr */
|
|
42
|
+
/** @typedef {{type: "IfExpr", condition: Expr, thenExpr: Expr, elseExpr: Expr, start: number, end: number}} IfExpr */
|
|
43
|
+
/** @typedef {{type: "LogicalExpr", operator: "or"|"and", left: Expr, right: Expr, start: number, end: number}} LogicalExpr */
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Comparison; `kind` is "general" (= != < <= > >=), "value" (eq ne lt le gt
|
|
47
|
+
* ge) or "node" (is << >>). Comparisons do not chain.
|
|
48
|
+
*
|
|
49
|
+
* @typedef {{type: "ComparisonExpr", kind: "general"|"value"|"node", operator: string, left: Expr, right: Expr, start: number, end: number}} ComparisonExpr
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/** @typedef {{type: "StringConcatExpr", left: Expr, right: Expr, start: number, end: number}} StringConcatExpr `A || B` */
|
|
53
|
+
/** @typedef {{type: "RangeExpr", left: Expr, right: Expr, start: number, end: number}} RangeExpr `A to B` */
|
|
54
|
+
/** @typedef {{type: "ArithmeticExpr", operator: "+"|"-"|"*"|"div"|"idiv"|"mod", left: Expr, right: Expr, start: number, end: number}} ArithmeticExpr */
|
|
55
|
+
/** @typedef {{type: "SetExpr", operator: "union"|"intersect"|"except", left: Expr, right: Expr, start: number, end: number}} SetExpr `|` is stored as "union" */
|
|
56
|
+
/** @typedef {{type: "InstanceOfExpr", expr: Expr, sequenceType: import("./typeAst.js").SequenceType, start: number, end: number}} InstanceOfExpr */
|
|
57
|
+
/** @typedef {{type: "TreatExpr", expr: Expr, sequenceType: import("./typeAst.js").SequenceType, start: number, end: number}} TreatExpr */
|
|
58
|
+
/** @typedef {{type: "CastableExpr", expr: Expr, targetType: QName, emptyAllowed: boolean, start: number, end: number}} CastableExpr `E castable as T?` */
|
|
59
|
+
/** @typedef {{type: "CastExpr", expr: Expr, targetType: QName, emptyAllowed: boolean, start: number, end: number}} CastExpr `E cast as T?` */
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* `E => f(args)`, `E => $f(args)` or `E => (expr)(args)`: exactly one of
|
|
63
|
+
* `functionName` and `functionExpr` is set. Arguments do not include E.
|
|
64
|
+
*
|
|
65
|
+
* @typedef {{type: "ArrowExpr", expr: Expr, functionName: QName|null, functionExpr: Expr|null, arguments: Array<Expr|ArgumentPlaceholder>, start: number, end: number}} ArrowExpr
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/** @typedef {{type: "UnaryExpr", operator: "+"|"-", operand: Expr, start: number, end: number}} UnaryExpr `--E` nests two nodes */
|
|
69
|
+
/** @typedef {{type: "SimpleMapExpr", left: Expr, right: Expr, start: number, end: number}} SimpleMapExpr `A ! B` */
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A path with at least one `/` or `//`. `absolute` is true for a leading
|
|
73
|
+
* slash; a lone `/` has no steps. `//` is expanded to
|
|
74
|
+
* `/descendant-or-self::node()/`, so `//a` has two steps.
|
|
75
|
+
*
|
|
76
|
+
* @typedef {{type: "PathExpr", absolute: boolean, steps: Expr[], start: number, end: number}} PathExpr
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* An axis step, abbreviated or not: `@a` has axis "attribute", `..` is
|
|
81
|
+
* `parent::node()`, and a step without axis takes "child", or "attribute"
|
|
82
|
+
* for an attribute()/schema-attribute() test, or "namespace" for
|
|
83
|
+
* namespace-node() (XPath 3.1 section 3.3.5).
|
|
84
|
+
*
|
|
85
|
+
* @typedef {{type: "AxisStep", axis: string, nodeTest: import("./typeAst.js").NodeTest, predicates: Expr[], start: number, end: number}} AxisStep
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
/** @typedef {{type: "FilterExpr", base: Expr, predicate: Expr, start: number, end: number}} FilterExpr `E[P]` on a primary expression */
|
|
89
|
+
/** @typedef {{type: "DynamicFunctionCall", functionExpr: Expr, arguments: Array<Expr|ArgumentPlaceholder>, start: number, end: number}} DynamicFunctionCall `$f(1, ?)` */
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Lookup `E?key`. `keyKind` "name" (`key` is the NCName), "integer" (`key`
|
|
93
|
+
* is the lexical integer), "expr" (`key` is the parenthesized Expr) or
|
|
94
|
+
* "wildcard" (`?*`, `key` null).
|
|
95
|
+
*
|
|
96
|
+
* @typedef {{type: "Lookup", base: Expr, keyKind: "name"|"integer"|"expr"|"wildcard", key: string|Expr|null, start: number, end: number}} Lookup
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
/** @typedef {{type: "UnaryLookup", keyKind: "name"|"integer"|"expr"|"wildcard", key: string|Expr|null, start: number, end: number}} UnaryLookup `?key` on the context item */
|
|
100
|
+
/** @typedef {{type: "NumericLiteral", kind: "integer"|"decimal"|"double", value: string, start: number, end: number}} NumericLiteral `value` is the lexical form */
|
|
101
|
+
/** @typedef {{type: "StringLiteral", value: string, start: number, end: number}} StringLiteral Doubled quotes already unescaped */
|
|
102
|
+
/** @typedef {{type: "VarRef", name: QName, start: number, end: number}} VarRef */
|
|
103
|
+
/** @typedef {{type: "ContextItemExpr", start: number, end: number}} ContextItemExpr `.` */
|
|
104
|
+
/** @typedef {{type: "FunctionCall", name: QName, arguments: Array<Expr|ArgumentPlaceholder>, start: number, end: number}} FunctionCall With a placeholder it is a partial application */
|
|
105
|
+
/** @typedef {{type: "ArgumentPlaceholder", start: number, end: number}} ArgumentPlaceholder `?` as an argument */
|
|
106
|
+
/** @typedef {{type: "NamedFunctionRef", name: QName, arity: number, start: number, end: number}} NamedFunctionRef `name#arity` */
|
|
107
|
+
/** @typedef {{type: "Param", name: QName, sequenceType: import("./typeAst.js").SequenceType|null, start: number, end: number}} Param */
|
|
108
|
+
/** @typedef {{type: "InlineFunctionExpr", params: Param[], returnType: import("./typeAst.js").SequenceType|null, body: Expr, start: number, end: number}} InlineFunctionExpr An empty body `{}` is an EmptySequence */
|
|
109
|
+
/** @typedef {{type: "MapEntry", key: Expr, value: Expr, start: number, end: number}} MapEntry */
|
|
110
|
+
/** @typedef {{type: "MapConstructor", entries: MapEntry[], start: number, end: number}} MapConstructor `map { k : v, ... }` */
|
|
111
|
+
/** @typedef {{type: "SquareArrayConstructor", members: Expr[], start: number, end: number}} SquareArrayConstructor `[a, b]`: one member per expression */
|
|
112
|
+
/** @typedef {{type: "CurlyArrayConstructor", expr: Expr, start: number, end: number}} CurlyArrayConstructor `array { E }`: one member per item of E */
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Creates a node with the fields in a stable order: type, fields, offsets.
|
|
116
|
+
*
|
|
117
|
+
* @template {object} F
|
|
118
|
+
* @param {string} type - Node type
|
|
119
|
+
* @param {F} fields - Node-specific fields
|
|
120
|
+
* @param {number} start - Offset of the first character
|
|
121
|
+
* @param {number} end - Offset after the last character
|
|
122
|
+
* @returns {{type: string, start: number, end: number} & F} The node
|
|
123
|
+
*/
|
|
124
|
+
export function makeNode(type, fields, start, end) {
|
|
125
|
+
// Object.assign: several times faster than a spread in a literal here
|
|
126
|
+
const node = Object.assign({ type }, fields);
|
|
127
|
+
node.start = start;
|
|
128
|
+
node.end = end;
|
|
129
|
+
return node;
|
|
130
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Character classes of the XPath 3.1 lexical structure (Appendix A.2).
|
|
3
|
+
*
|
|
4
|
+
* Names use the XML 1.0 (fifth edition) name characters, which are also
|
|
5
|
+
* those of XML 1.1, so the xml-version constraint (A.1.2) needs no switch.
|
|
6
|
+
*
|
|
7
|
+
* @module @tradik/xslt3/xpath/syntax/chars
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const NAME_START =
|
|
11
|
+
"A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D" +
|
|
12
|
+
"\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF" +
|
|
13
|
+
"\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\u{10000}-\\u{EFFFF}";
|
|
14
|
+
const NAME_CHAR = `${NAME_START}\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040`;
|
|
15
|
+
|
|
16
|
+
/** NCName (sticky). */
|
|
17
|
+
// The combining marks U+0300-U+036F are NameChars on their own, as XML says.
|
|
18
|
+
// eslint-disable-next-line no-misleading-character-class
|
|
19
|
+
export const NCNAME = new RegExp(`[${NAME_START}][${NAME_CHAR}]*`, "uy");
|
|
20
|
+
/** One NameStartChar without ":" (sticky). */
|
|
21
|
+
export const NAME_START_CHAR = new RegExp(`[${NAME_START}]`, "uy");
|
|
22
|
+
/** IntegerLiteral, DecimalLiteral or DoubleLiteral (sticky). */
|
|
23
|
+
export const NUMBER = /(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)(?:[eE][+-]?[0-9]+)?/y;
|
|
24
|
+
/** XML whitespace (sticky). */
|
|
25
|
+
export const WHITESPACE = /[ \t\r\n]+/y;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns the match of a sticky regular expression at an offset.
|
|
29
|
+
*
|
|
30
|
+
* @param {RegExp} pattern - Sticky (`y`) pattern
|
|
31
|
+
* @param {string} text - Source
|
|
32
|
+
* @param {number} offset - Where the match must start
|
|
33
|
+
* @returns {string|null} The matched text, or null
|
|
34
|
+
*/
|
|
35
|
+
export function matchAt(pattern, text, offset) {
|
|
36
|
+
pattern.lastIndex = offset;
|
|
37
|
+
const match = pattern.exec(text);
|
|
38
|
+
return match ? match[0] : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {string} text - Candidate name
|
|
43
|
+
* @returns {boolean} Whether the whole text is an NCName
|
|
44
|
+
*/
|
|
45
|
+
export function isNCName(text) {
|
|
46
|
+
return matchAt(NCNAME, text, 0) === text;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Whitespace normalization of xs:anyURI and normalize-space(): runs of
|
|
51
|
+
* whitespace become one space, leading and trailing ones are removed.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} text - Text
|
|
54
|
+
* @returns {string} Normalized text
|
|
55
|
+
*/
|
|
56
|
+
export function normalizeSpace(text) {
|
|
57
|
+
return text.replace(/[ \t\r\n]+/g, " ").trim();
|
|
58
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser of the XPath 3.1 constructors: inline function expressions
|
|
3
|
+
* (productions [69] InlineFunctionExpr, [10] ParamList, [11] Param), maps
|
|
4
|
+
* ([69] MapConstructor to [72] MapValueExpr) and arrays ([73]
|
|
5
|
+
* ArrayConstructor to [75] CurlyArrayConstructor) of Appendix A.1.
|
|
6
|
+
*
|
|
7
|
+
* @module @tradik/xslt3/xpath/syntax/constructors
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { makeNode } from "./ast.js";
|
|
11
|
+
import { parseExpr, parseExprSingle } from "./expressions.js";
|
|
12
|
+
import { qName } from "./tokenStream.js";
|
|
13
|
+
import { parseSequenceType } from "./types.js";
|
|
14
|
+
|
|
15
|
+
/** @typedef {import("./tokenStream.js").TokenStream} TokenStream */
|
|
16
|
+
/** @typedef {import("./ast.js").Expr} Expr */
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* EnclosedExpr ::= "{" Expr? "}"; an empty one gives an EmptySequence.
|
|
20
|
+
*
|
|
21
|
+
* @param {TokenStream} p - Tokens, at "{"
|
|
22
|
+
* @returns {{expr: Expr, end: number}} The content and the offset after "}"
|
|
23
|
+
*/
|
|
24
|
+
function parseEnclosedExpr(p) {
|
|
25
|
+
const open = p.expectSymbol("{");
|
|
26
|
+
const close = p.acceptSymbol("}");
|
|
27
|
+
if (close) {
|
|
28
|
+
return {
|
|
29
|
+
expr: makeNode("EmptySequence", {}, open.start, close.end),
|
|
30
|
+
end: close.end,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const expr = parseExpr(p);
|
|
34
|
+
return { expr, end: p.expectSymbol("}").end };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Reads `item ("," item)*` up to a closing symbol, which is consumed.
|
|
39
|
+
*
|
|
40
|
+
* @template T
|
|
41
|
+
* @param {TokenStream} p - Tokens, after the opening symbol
|
|
42
|
+
* @param {string} close - Closing symbol
|
|
43
|
+
* @param {(p: TokenStream) => T} parseItem - Parser of one item
|
|
44
|
+
* @returns {{items: T[], end: number}} The items and the offset after close
|
|
45
|
+
*/
|
|
46
|
+
function parseList(p, close, parseItem) {
|
|
47
|
+
const items = [];
|
|
48
|
+
if (!p.isSymbol(close)) {
|
|
49
|
+
do items.push(parseItem(p));
|
|
50
|
+
while (p.acceptSymbol(","));
|
|
51
|
+
}
|
|
52
|
+
return { items, end: p.expectSymbol(close).end };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* InlineFunctionExpr ::= "function" "(" ParamList? ")" ("as" SequenceType)?
|
|
57
|
+
* FunctionBody
|
|
58
|
+
*
|
|
59
|
+
* @param {TokenStream} p - Tokens, at "function"
|
|
60
|
+
* @returns {import("./ast.js").InlineFunctionExpr} The function
|
|
61
|
+
*/
|
|
62
|
+
export function parseInlineFunctionExpr(p) {
|
|
63
|
+
const start = p.next().start;
|
|
64
|
+
p.expectSymbol("(");
|
|
65
|
+
const { items: params } = parseList(p, ")", parseParam);
|
|
66
|
+
const returnType = p.acceptKeyword("as") ? parseSequenceType(p) : null;
|
|
67
|
+
const { expr: body, end } = parseEnclosedExpr(p);
|
|
68
|
+
return makeNode(
|
|
69
|
+
"InlineFunctionExpr",
|
|
70
|
+
{ params, returnType, body },
|
|
71
|
+
start,
|
|
72
|
+
end,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Param ::= "$" EQName TypeDeclaration?
|
|
78
|
+
*
|
|
79
|
+
* @param {TokenStream} p - Tokens
|
|
80
|
+
* @returns {import("./ast.js").Param} The parameter
|
|
81
|
+
*/
|
|
82
|
+
function parseParam(p) {
|
|
83
|
+
const dollar = p.expectSymbol("$");
|
|
84
|
+
const name = p.expectName("a parameter name");
|
|
85
|
+
const sequenceType = p.acceptKeyword("as") ? parseSequenceType(p) : null;
|
|
86
|
+
const end = sequenceType ? sequenceType.end : name.end;
|
|
87
|
+
return makeNode(
|
|
88
|
+
"Param",
|
|
89
|
+
{ name: qName(name), sequenceType },
|
|
90
|
+
dollar.start,
|
|
91
|
+
end,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* MapConstructor ::= "map" "{" (MapConstructorEntry ("," ...)*)? "}"
|
|
97
|
+
*
|
|
98
|
+
* @param {TokenStream} p - Tokens, at "map"
|
|
99
|
+
* @returns {import("./ast.js").MapConstructor} The constructor
|
|
100
|
+
*/
|
|
101
|
+
export function parseMapConstructor(p) {
|
|
102
|
+
const start = p.next().start;
|
|
103
|
+
p.expectSymbol("{");
|
|
104
|
+
const { items: entries, end } = parseList(p, "}", parseMapEntry);
|
|
105
|
+
return makeNode("MapConstructor", { entries }, start, end);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* MapConstructorEntry ::= ExprSingle ":" ExprSingle. Note that `map{a:b}`
|
|
110
|
+
* reads the QName `a:b`; a key that is a name needs a space: `map{a :b}`.
|
|
111
|
+
*
|
|
112
|
+
* @param {TokenStream} p - Tokens
|
|
113
|
+
* @returns {import("./ast.js").MapEntry} The entry
|
|
114
|
+
*/
|
|
115
|
+
function parseMapEntry(p) {
|
|
116
|
+
const key = parseExprSingle(p);
|
|
117
|
+
p.expectSymbol(":");
|
|
118
|
+
const value = parseExprSingle(p);
|
|
119
|
+
return makeNode("MapEntry", { key, value }, key.start, value.end);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* SquareArrayConstructor ::= "[" (ExprSingle ("," ExprSingle)*)? "]"
|
|
124
|
+
*
|
|
125
|
+
* @param {TokenStream} p - Tokens, at "["
|
|
126
|
+
* @returns {import("./ast.js").SquareArrayConstructor} The constructor
|
|
127
|
+
*/
|
|
128
|
+
export function parseSquareArrayConstructor(p) {
|
|
129
|
+
const start = p.next().start;
|
|
130
|
+
const { items: members, end } = parseList(p, "]", parseExprSingle);
|
|
131
|
+
return makeNode("SquareArrayConstructor", { members }, start, end);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* CurlyArrayConstructor ::= "array" EnclosedExpr
|
|
136
|
+
*
|
|
137
|
+
* @param {TokenStream} p - Tokens, at "array"
|
|
138
|
+
* @returns {import("./ast.js").CurlyArrayConstructor} The constructor
|
|
139
|
+
*/
|
|
140
|
+
export function parseCurlyArrayConstructor(p) {
|
|
141
|
+
const start = p.next().start;
|
|
142
|
+
const { expr, end } = parseEnclosedExpr(p);
|
|
143
|
+
return makeNode("CurlyArrayConstructor", { expr }, start, end);
|
|
144
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser of the top of the XPath 3.1 grammar: Expr (the comma operator),
|
|
3
|
+
* ExprSingle and the expressions introduced by a keyword (for, let, some,
|
|
4
|
+
* every, if). Productions [6] to [15] of XPath 3.1 Appendix A.1.
|
|
5
|
+
*
|
|
6
|
+
* Each function takes the {@link TokenStream} and returns an AST node.
|
|
7
|
+
*
|
|
8
|
+
* @module @tradik/xslt3/xpath/syntax/expressions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { makeNode } from "./ast.js";
|
|
12
|
+
import { parseOrExpr } from "./operators.js";
|
|
13
|
+
import { qName } from "./tokenStream.js";
|
|
14
|
+
|
|
15
|
+
/** @typedef {import("./tokenStream.js").TokenStream} TokenStream */
|
|
16
|
+
/** @typedef {import("./ast.js").Expr} Expr */
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Expr ::= ExprSingle ("," ExprSingle)*
|
|
20
|
+
*
|
|
21
|
+
* @param {TokenStream} p - Tokens
|
|
22
|
+
* @returns {Expr} A SequenceExpr for two or more items, else the item
|
|
23
|
+
*/
|
|
24
|
+
export function parseExpr(p) {
|
|
25
|
+
const first = parseExprSingle(p);
|
|
26
|
+
if (!p.isSymbol(",")) return first;
|
|
27
|
+
const items = [first];
|
|
28
|
+
while (p.acceptSymbol(",")) items.push(parseExprSingle(p));
|
|
29
|
+
return makeNode("SequenceExpr", { items }, first.start, items.at(-1).end);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* ExprSingle ::= ForExpr | LetExpr | QuantifiedExpr | IfExpr | OrExpr.
|
|
34
|
+
* A keyword starts its expression only when followed by "$" (or "(" for
|
|
35
|
+
* `if`); otherwise it is an ordinary name, e.g. the element `for`.
|
|
36
|
+
*
|
|
37
|
+
* @param {TokenStream} p - Tokens
|
|
38
|
+
* @returns {Expr} The expression
|
|
39
|
+
*/
|
|
40
|
+
export function parseExprSingle(p) {
|
|
41
|
+
if (p.isSymbol("$", 1)) {
|
|
42
|
+
if (p.isKeyword("for")) {
|
|
43
|
+
return parseBindingExpr(p, "ForExpr", "in", "return");
|
|
44
|
+
}
|
|
45
|
+
if (p.isKeyword("let")) {
|
|
46
|
+
return parseBindingExpr(p, "LetExpr", ":=", "return");
|
|
47
|
+
}
|
|
48
|
+
if (p.isKeyword("some") || p.isKeyword("every")) {
|
|
49
|
+
return parseBindingExpr(p, "QuantifiedExpr", "in", "satisfies");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (p.isKeyword("if") && p.isSymbol("(", 1)) return parseIfExpr(p);
|
|
53
|
+
return parseOrExpr(p);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* ForExpr, LetExpr and QuantifiedExpr, which share their shape:
|
|
58
|
+
* keyword binding ("," binding)* final-keyword ExprSingle.
|
|
59
|
+
*
|
|
60
|
+
* @param {TokenStream} p - Tokens
|
|
61
|
+
* @param {"ForExpr"|"LetExpr"|"QuantifiedExpr"} type - Node type
|
|
62
|
+
* @param {"in"|":="} separator - Between variable and value
|
|
63
|
+
* @param {"return"|"satisfies"} finalKeyword - Before the result expression
|
|
64
|
+
* @returns {Expr} The node
|
|
65
|
+
*/
|
|
66
|
+
function parseBindingExpr(p, type, separator, finalKeyword) {
|
|
67
|
+
const keyword = p.next();
|
|
68
|
+
const bindings = [];
|
|
69
|
+
do {
|
|
70
|
+
const dollar = p.expectSymbol("$");
|
|
71
|
+
const name = qName(p.expectName("a variable name"));
|
|
72
|
+
if (separator === "in") p.expectKeyword("in");
|
|
73
|
+
else p.expectSymbol(":=");
|
|
74
|
+
const expr = parseExprSingle(p);
|
|
75
|
+
bindings.push(makeNode("Binding", { name, expr }, dollar.start, expr.end));
|
|
76
|
+
} while (p.acceptSymbol(","));
|
|
77
|
+
p.expectKeyword(finalKeyword);
|
|
78
|
+
const result = parseExprSingle(p);
|
|
79
|
+
const fields =
|
|
80
|
+
type === "QuantifiedExpr"
|
|
81
|
+
? { quantifier: keyword.local, bindings, satisfies: result }
|
|
82
|
+
: { bindings, returnExpr: result };
|
|
83
|
+
return makeNode(type, fields, keyword.start, result.end);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* IfExpr ::= "if" "(" Expr ")" "then" ExprSingle "else" ExprSingle
|
|
88
|
+
*
|
|
89
|
+
* @param {TokenStream} p - Tokens
|
|
90
|
+
* @returns {import("./ast.js").IfExpr} The node
|
|
91
|
+
*/
|
|
92
|
+
function parseIfExpr(p) {
|
|
93
|
+
const start = p.next().start;
|
|
94
|
+
p.expectSymbol("(");
|
|
95
|
+
const condition = parseExpr(p);
|
|
96
|
+
p.expectSymbol(")");
|
|
97
|
+
p.expectKeyword("then");
|
|
98
|
+
const thenExpr = parseExprSingle(p);
|
|
99
|
+
p.expectKeyword("else");
|
|
100
|
+
const elseExpr = parseExprSingle(p);
|
|
101
|
+
return makeNode(
|
|
102
|
+
"IfExpr",
|
|
103
|
+
{ condition, thenExpr, elseExpr },
|
|
104
|
+
start,
|
|
105
|
+
elseExpr.end,
|
|
106
|
+
);
|
|
107
|
+
}
|