@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,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fn:random-number-generator (F&O 3.1 section 17.3.1): a map with a
|
|
3
|
+
* pseudo-random `number` in [0, 1), a `next` generator and a `permute`
|
|
4
|
+
* function. The sequence is deterministic for a seed (the canonical string
|
|
5
|
+
* of the seed value, hashed with FNV-1a) and uses the Mulberry32
|
|
6
|
+
* generator; without a seed, the current date and time of the evaluation
|
|
7
|
+
* is the seed, so calls in one evaluation agree.
|
|
8
|
+
*
|
|
9
|
+
* @module @tradik/xslt3/functions/random
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { FunctionItem } from "../items/function.js";
|
|
13
|
+
import { XdmMap } from "../items/map.js";
|
|
14
|
+
import { formatDateTime } from "../xdm/datetime.js";
|
|
15
|
+
import { canonicalString } from "../xdm/lexical.js";
|
|
16
|
+
import { sequenceTypeOf } from "../xpath/eval/functionItems.js";
|
|
17
|
+
import { define, doubleItem, stringItem } from "./support.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* FNV-1a hash of a string.
|
|
21
|
+
* @param {string} text
|
|
22
|
+
* @returns {number} an unsigned 32-bit integer
|
|
23
|
+
*/
|
|
24
|
+
export function hashSeed(text) {
|
|
25
|
+
let hash = 0x811c9dc5;
|
|
26
|
+
for (let i = 0; i < text.length; i++) {
|
|
27
|
+
hash = Math.imul(hash ^ text.charCodeAt(i), 0x01000193);
|
|
28
|
+
}
|
|
29
|
+
return hash >>> 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* One step of Mulberry32.
|
|
34
|
+
* @param {number} state - Unsigned 32-bit state
|
|
35
|
+
* @returns {[number, number]} a number in [0, 1) and the next state
|
|
36
|
+
*/
|
|
37
|
+
export function step(state) {
|
|
38
|
+
const next = (state + 0x6d2b79f5) >>> 0;
|
|
39
|
+
let t = next;
|
|
40
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
41
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
42
|
+
return [((t ^ (t >>> 14)) >>> 0) / 4294967296, next];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A random permutation (Fisher-Yates) of a sequence.
|
|
47
|
+
* @param {Array} items
|
|
48
|
+
* @param {number} state
|
|
49
|
+
* @returns {Array}
|
|
50
|
+
*/
|
|
51
|
+
function permute(items, state) {
|
|
52
|
+
const result = [...items];
|
|
53
|
+
let s = state;
|
|
54
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
55
|
+
const [r, next] = step(s);
|
|
56
|
+
s = next;
|
|
57
|
+
const j = Math.floor(r * (i + 1));
|
|
58
|
+
[result[i], result[j]] = [result[j], result[i]];
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const NEXT = {
|
|
64
|
+
params: [],
|
|
65
|
+
returns: sequenceTypeOf("map(xs:string, item())"),
|
|
66
|
+
};
|
|
67
|
+
const PERMUTE = {
|
|
68
|
+
params: [sequenceTypeOf("item()*")],
|
|
69
|
+
returns: sequenceTypeOf("item()*"),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The generator map of a state.
|
|
74
|
+
* @param {number} state
|
|
75
|
+
* @returns {XdmMap}
|
|
76
|
+
*/
|
|
77
|
+
export function generator(state) {
|
|
78
|
+
const [number, next] = step(state);
|
|
79
|
+
return XdmMap.from([
|
|
80
|
+
[stringItem("number"), [doubleItem(number)]],
|
|
81
|
+
[
|
|
82
|
+
stringItem("next"),
|
|
83
|
+
[
|
|
84
|
+
new FunctionItem({
|
|
85
|
+
arity: 0,
|
|
86
|
+
signature: NEXT,
|
|
87
|
+
invoke: () => [generator(next)],
|
|
88
|
+
}),
|
|
89
|
+
],
|
|
90
|
+
],
|
|
91
|
+
[
|
|
92
|
+
stringItem("permute"),
|
|
93
|
+
[
|
|
94
|
+
new FunctionItem({
|
|
95
|
+
arity: 1,
|
|
96
|
+
signature: PERMUTE,
|
|
97
|
+
invoke: ([items]) => permute(items, hashSeed(`p${next}`)),
|
|
98
|
+
}),
|
|
99
|
+
],
|
|
100
|
+
],
|
|
101
|
+
]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The seed of an optional atomic value.
|
|
106
|
+
* @param {Array} seed
|
|
107
|
+
* @param {object} context - Gives the current date and time
|
|
108
|
+
* @returns {number}
|
|
109
|
+
*/
|
|
110
|
+
const seedOf = (seed, context) =>
|
|
111
|
+
hashSeed(
|
|
112
|
+
seed.length
|
|
113
|
+
? `${seed[0].type.primitive.localName}:${canonicalString(seed[0])}`
|
|
114
|
+
: formatDateTime("dateTime", context.currentDateTime),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
/** Function definitions. */
|
|
118
|
+
export const randomFunctions = [
|
|
119
|
+
define("random-number-generator", [], "map(xs:string, item())", (_, c) => [
|
|
120
|
+
generator(seedOf([], c)),
|
|
121
|
+
]),
|
|
122
|
+
define(
|
|
123
|
+
"random-number-generator",
|
|
124
|
+
["xs:anyAtomicType?"],
|
|
125
|
+
"map(xs:string, item())",
|
|
126
|
+
([seed], c) => [generator(seedOf(seed, c))],
|
|
127
|
+
),
|
|
128
|
+
];
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fn:analyze-string (F&O 3.1 section 5.6.6).
|
|
3
|
+
*
|
|
4
|
+
* The result is computed as a plain tree, `{ name, attributes, children }`
|
|
5
|
+
* with string children for text, and then built as DOM nodes through the
|
|
6
|
+
* dynamic context hook `context.createDocument()`, which must return an
|
|
7
|
+
* empty DOM Document (createElementNS, createTextNode, setAttribute and
|
|
8
|
+
* appendChild are used). The element is appended to that document, as
|
|
9
|
+
* its document element, and returned. Without the hook the plain tree is
|
|
10
|
+
* returned as is.
|
|
11
|
+
*
|
|
12
|
+
* @module @tradik/xslt3/functions/regex/analyzeString
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { define, FN_NAMESPACE, stringArg } from "../support.js";
|
|
16
|
+
import { allMatches, compileRegex, rejectEmptyMatch } from "./compile.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {object} ResultElement
|
|
20
|
+
* @property {string} name - Local name in the fn namespace
|
|
21
|
+
* @property {Record<string, string>} attributes
|
|
22
|
+
* @property {Array<ResultElement|string>} children
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** @returns {ResultElement} */
|
|
26
|
+
const element = (name, children, attributes = {}) => ({
|
|
27
|
+
name,
|
|
28
|
+
attributes,
|
|
29
|
+
children,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Children of a match or group: text and the nested groups.
|
|
34
|
+
* @param {RegExpExecArray} match - With indices
|
|
35
|
+
* @param {import("./compile.js").CompiledRegex} compiled
|
|
36
|
+
* @param {number} parent - Group number, 0 for the whole match
|
|
37
|
+
* @param {[number, number]} range - Offsets of the parent in the input
|
|
38
|
+
* @returns {Array<ResultElement|string>}
|
|
39
|
+
*/
|
|
40
|
+
function groupChildren(match, compiled, parent, [start, end]) {
|
|
41
|
+
const input = match.input;
|
|
42
|
+
const children = [];
|
|
43
|
+
let position = start;
|
|
44
|
+
for (let group = 1; group <= compiled.groups; group++) {
|
|
45
|
+
const span = match.indices[group];
|
|
46
|
+
// a group of an earlier iteration of a repeated parent lies outside it
|
|
47
|
+
if (
|
|
48
|
+
compiled.parents[group] !== parent ||
|
|
49
|
+
span === undefined ||
|
|
50
|
+
span[0] < position ||
|
|
51
|
+
span[1] > end
|
|
52
|
+
) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (span[0] > position) children.push(input.slice(position, span[0]));
|
|
56
|
+
children.push(
|
|
57
|
+
element("group", groupChildren(match, compiled, group, span), {
|
|
58
|
+
nr: String(group),
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
position = span[1];
|
|
62
|
+
}
|
|
63
|
+
if (position < end) children.push(input.slice(position, end));
|
|
64
|
+
return children;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The fn:analyze-string-result tree of a string.
|
|
69
|
+
* @param {string} input
|
|
70
|
+
* @param {string} pattern
|
|
71
|
+
* @param {string} [flags=""]
|
|
72
|
+
* @returns {ResultElement}
|
|
73
|
+
* @throws {XPathError} FORX0001, FORX0002, FORX0003
|
|
74
|
+
*/
|
|
75
|
+
export function analyzeStringTree(input, pattern, flags = "") {
|
|
76
|
+
const compiled = compileRegex(pattern, flags);
|
|
77
|
+
rejectEmptyMatch(compiled);
|
|
78
|
+
const children = [];
|
|
79
|
+
let last = 0;
|
|
80
|
+
for (const match of allMatches(compiled, input)) {
|
|
81
|
+
if (match.index > last) {
|
|
82
|
+
children.push(element("non-match", [input.slice(last, match.index)]));
|
|
83
|
+
}
|
|
84
|
+
children.push(
|
|
85
|
+
element("match", groupChildren(match, compiled, 0, match.indices[0])),
|
|
86
|
+
);
|
|
87
|
+
last = match.index + match[0].length;
|
|
88
|
+
}
|
|
89
|
+
if (last < input.length) {
|
|
90
|
+
children.push(element("non-match", [input.slice(last)]));
|
|
91
|
+
}
|
|
92
|
+
return element("analyze-string-result", children);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Builds a result tree as DOM nodes.
|
|
97
|
+
* @param {ResultElement} tree
|
|
98
|
+
* @param {Document} doc
|
|
99
|
+
* @returns {Element}
|
|
100
|
+
*/
|
|
101
|
+
export function toDom(tree, doc) {
|
|
102
|
+
const node = doc.createElementNS(FN_NAMESPACE, tree.name);
|
|
103
|
+
for (const [name, value] of Object.entries(tree.attributes)) {
|
|
104
|
+
node.setAttribute(name, value);
|
|
105
|
+
}
|
|
106
|
+
for (const child of tree.children) {
|
|
107
|
+
node.appendChild(
|
|
108
|
+
typeof child === "string" ? doc.createTextNode(child) : toDom(child, doc),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return node;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* fn:analyze-string implementation.
|
|
116
|
+
* @param {Array<*>[]} args - [$input, $pattern, $flags?]
|
|
117
|
+
* @param {{createDocument?: () => Document}} context
|
|
118
|
+
* @returns {Array<*>}
|
|
119
|
+
*/
|
|
120
|
+
function analyzeString([input, [pattern], flags], context) {
|
|
121
|
+
const tree = analyzeStringTree(
|
|
122
|
+
stringArg(input),
|
|
123
|
+
pattern.value,
|
|
124
|
+
flags === undefined ? "" : flags[0].value,
|
|
125
|
+
);
|
|
126
|
+
const doc = context.createDocument?.();
|
|
127
|
+
if (!doc) return [tree];
|
|
128
|
+
const root = toDom(tree, doc);
|
|
129
|
+
doc.appendChild(root);
|
|
130
|
+
return [root];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const RESULT = "element(fn:analyze-string-result)";
|
|
134
|
+
|
|
135
|
+
/** @type {import("../support.js").FunctionDefinition[]} */
|
|
136
|
+
export const analyzeStringFunctions = [
|
|
137
|
+
define("analyze-string", ["xs:string?", "xs:string"], RESULT, analyzeString),
|
|
138
|
+
define(
|
|
139
|
+
"analyze-string",
|
|
140
|
+
["xs:string?", "xs:string", "xs:string"],
|
|
141
|
+
RESULT,
|
|
142
|
+
analyzeString,
|
|
143
|
+
),
|
|
144
|
+
];
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Character classes of XPath regular expressions (XSD 1.1
|
|
3
|
+
* Part 2 appendix G with the F&O 3.1 section 5.6.1 extensions), translated
|
|
4
|
+
* to JavaScript `u`-mode syntax.
|
|
5
|
+
*
|
|
6
|
+
* A class item is either `{ set }`, text that fits inside a JS `[...]`
|
|
7
|
+
* (characters, ranges, `\p{..}`), or `{ atom }`, a standalone JS
|
|
8
|
+
* expression matching one character (used for negated sets such as `\w`
|
|
9
|
+
* and `\I`, which cannot be nested in a JS class). Single characters also
|
|
10
|
+
* carry `char`, their codepoint, so they can start or end a range.
|
|
11
|
+
* Subtraction `[a-z-[aeiou]]` and negated mixtures become a negative
|
|
12
|
+
* lookahead in the generated expression: `(?:(?![aeiou])[a-z])`.
|
|
13
|
+
*
|
|
14
|
+
* @module @tradik/xslt3/functions/regex/charClass
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { jsChar, parseEscape, regexError } from "./escapes.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* One class character: an escape or a literal character. An unescaped
|
|
21
|
+
* "-" is a literal at the start or end of the group and right after a
|
|
22
|
+
* range (as in [a-c-x], XSD 1.1); it then cannot start a range.
|
|
23
|
+
* @param {import("./reader.js").RegexReader} reader
|
|
24
|
+
* @param {boolean} hyphenAllowed - First item, or after a range
|
|
25
|
+
* @returns {{set?: string, atom?: string, char?: number, hyphen?: boolean}}
|
|
26
|
+
*/
|
|
27
|
+
function classChar(reader, hyphenAllowed) {
|
|
28
|
+
const c = reader.next();
|
|
29
|
+
if (c === "\\") return parseEscape(reader);
|
|
30
|
+
if (c === "[") regexError("unescaped [ in a character class");
|
|
31
|
+
const cp = c.codePointAt(0);
|
|
32
|
+
if (c === "-" && reader.peek() !== "]") {
|
|
33
|
+
if (!hyphenAllowed) regexError("misplaced - in a character class");
|
|
34
|
+
return { char: cp, set: jsChar(cp), hyphen: true };
|
|
35
|
+
}
|
|
36
|
+
return { char: cp, set: jsChar(cp) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A JS expression matching one character of any of the items.
|
|
41
|
+
* @param {Array<{set?: string, atom?: string}>} items
|
|
42
|
+
* @param {boolean} negated
|
|
43
|
+
* @returns {string}
|
|
44
|
+
*/
|
|
45
|
+
function itemsToAtom(items, negated) {
|
|
46
|
+
const set = items.map((item) => item.set ?? "").join("");
|
|
47
|
+
const atoms = items.filter((item) => item.set === undefined);
|
|
48
|
+
if (atoms.length === 0) return `[${negated ? "^" : ""}${set}]`;
|
|
49
|
+
const alternatives = (set ? [`[${set}]`] : []).concat(
|
|
50
|
+
atoms.map((item) => item.atom),
|
|
51
|
+
);
|
|
52
|
+
const positive =
|
|
53
|
+
alternatives.length === 1
|
|
54
|
+
? alternatives[0]
|
|
55
|
+
: `(?:${alternatives.join("|")})`;
|
|
56
|
+
return negated ? `(?:(?!${positive})[^])` : positive;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parses a character class expression; the reader is after its "[".
|
|
61
|
+
* @param {import("./reader.js").RegexReader} reader
|
|
62
|
+
* @returns {string} JS expression matching one character
|
|
63
|
+
* @throws {XPathError} FORX0002 for invalid classes
|
|
64
|
+
*/
|
|
65
|
+
export function parseCharClass(reader) {
|
|
66
|
+
const negated = reader.peek() === "^";
|
|
67
|
+
if (negated) reader.next();
|
|
68
|
+
const items = [];
|
|
69
|
+
let subtraction = null;
|
|
70
|
+
for (;;) {
|
|
71
|
+
const c = reader.peek();
|
|
72
|
+
if (c === undefined) regexError("unterminated character class");
|
|
73
|
+
if (items.length > 0 && c === "-" && reader.peek(1) === "[") {
|
|
74
|
+
reader.next();
|
|
75
|
+
reader.next();
|
|
76
|
+
subtraction = parseCharClass(reader);
|
|
77
|
+
if (reader.next() !== "]") regexError("expected ] after subtraction");
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
if (c === "]") {
|
|
81
|
+
reader.next();
|
|
82
|
+
if (items.length === 0) regexError("empty character class");
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
const item = classChar(
|
|
86
|
+
reader,
|
|
87
|
+
items.length === 0 || items.at(-1).range === true,
|
|
88
|
+
);
|
|
89
|
+
const next = reader.peek(1);
|
|
90
|
+
if (
|
|
91
|
+
item.char === undefined ||
|
|
92
|
+
(item.hyphen && items.length > 0) ||
|
|
93
|
+
reader.peek() !== "-" ||
|
|
94
|
+
next === "]"
|
|
95
|
+
) {
|
|
96
|
+
items.push(item);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (next === "[" || next === undefined) {
|
|
100
|
+
items.push(item);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
reader.next();
|
|
104
|
+
const end = classChar(reader, false);
|
|
105
|
+
if (end.char === undefined || end.char < item.char) {
|
|
106
|
+
regexError("invalid character range");
|
|
107
|
+
}
|
|
108
|
+
items.push({ set: `${item.set}-${end.set}`, range: true });
|
|
109
|
+
}
|
|
110
|
+
const atom = itemsToAtom(items, negated);
|
|
111
|
+
return subtraction === null ? atom : `(?:(?!${subtraction})${atom})`;
|
|
112
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compilation of XPath regular expressions to cached JS RegExp objects.
|
|
3
|
+
*
|
|
4
|
+
* @module @tradik/xslt3/functions/regex/compile
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { XPathError } from "../../errors.js";
|
|
8
|
+
import { regexError } from "./escapes.js";
|
|
9
|
+
import { translateRegex } from "./translate.js";
|
|
10
|
+
|
|
11
|
+
/** Maximum number of compiled expressions kept. */
|
|
12
|
+
const CACHE_SIZE = 256;
|
|
13
|
+
const cache = new Map();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {import("./translate.js").TranslatedRegex & {
|
|
17
|
+
* regex: RegExp, literal: boolean}} CompiledRegex
|
|
18
|
+
* `regex` has the `g` and `d` flags; `literal` is true for the q flag.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Translates and compiles a pattern, reusing earlier compilations.
|
|
23
|
+
* @param {string} pattern
|
|
24
|
+
* @param {string} [flags=""]
|
|
25
|
+
* @returns {CompiledRegex}
|
|
26
|
+
* @throws {XPathError} FORX0001, FORX0002
|
|
27
|
+
*/
|
|
28
|
+
export function compileRegex(pattern, flags = "") {
|
|
29
|
+
const key = `${flags}\u0000${pattern}`;
|
|
30
|
+
let compiled = cache.get(key);
|
|
31
|
+
if (compiled === undefined) {
|
|
32
|
+
const translated = translateRegex(pattern, flags);
|
|
33
|
+
let regex;
|
|
34
|
+
// the translator only emits valid syntax: the catch is a safety net
|
|
35
|
+
try {
|
|
36
|
+
regex = new RegExp(translated.source, `${translated.flags}gd`);
|
|
37
|
+
/* node:coverage ignore next 3 */
|
|
38
|
+
} catch (error) {
|
|
39
|
+
regexError(error.message);
|
|
40
|
+
}
|
|
41
|
+
compiled = { ...translated, regex, literal: flags.includes("q") };
|
|
42
|
+
if (cache.size >= CACHE_SIZE) cache.clear();
|
|
43
|
+
cache.set(key, compiled);
|
|
44
|
+
}
|
|
45
|
+
return compiled;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* All matches of a compiled expression in a string, left to right.
|
|
50
|
+
* @param {CompiledRegex} compiled
|
|
51
|
+
* @param {string} input
|
|
52
|
+
* @returns {RegExpExecArray[]} with `indices`
|
|
53
|
+
*/
|
|
54
|
+
export function allMatches(compiled, input) {
|
|
55
|
+
compiled.regex.lastIndex = 0;
|
|
56
|
+
return Array.from(input.matchAll(compiled.regex));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Whether a compiled expression matches somewhere in a string.
|
|
61
|
+
* @param {CompiledRegex} compiled
|
|
62
|
+
* @param {string} input
|
|
63
|
+
* @returns {boolean}
|
|
64
|
+
*/
|
|
65
|
+
export function testRegex(compiled, input) {
|
|
66
|
+
compiled.regex.lastIndex = 0;
|
|
67
|
+
return compiled.regex.test(input);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {CompiledRegex} compiled
|
|
72
|
+
* @throws {XPathError} FORX0003 when the expression matches ""
|
|
73
|
+
*/
|
|
74
|
+
export function rejectEmptyMatch(compiled) {
|
|
75
|
+
if (testRegex(compiled, "")) {
|
|
76
|
+
throw new XPathError(
|
|
77
|
+
"FORX0003",
|
|
78
|
+
"The regular expression matches a zero-length string",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escapes of XPath regular expressions (XSD 1.1 Part 2 appendix G and F&O
|
|
3
|
+
* 3.1 section 5.6.1): single-character escapes, multi-character escapes
|
|
4
|
+
* (\s \d \w \i \c and their complements) and \p{..}/\P{..} category and
|
|
5
|
+
* block escapes, as items for charClass.js and parser.js.
|
|
6
|
+
*
|
|
7
|
+
* @module @tradik/xslt3/functions/regex/escapes
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { XPathError } from "../../errors.js";
|
|
11
|
+
import { namePatterns } from "../../xdm/types.js";
|
|
12
|
+
import { blockRanges } from "./unicodeBlocks.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} message
|
|
16
|
+
* @returns {never}
|
|
17
|
+
* @throws {XPathError} FORX0002
|
|
18
|
+
*/
|
|
19
|
+
export function regexError(message) {
|
|
20
|
+
throw new XPathError("FORX0002", `Invalid regular expression: ${message}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A codepoint as JS regular expression text, valid inside and outside a
|
|
25
|
+
* class in `u` mode.
|
|
26
|
+
* @param {number} cp
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
export function jsChar(cp) {
|
|
30
|
+
const char = String.fromCodePoint(cp);
|
|
31
|
+
return /[A-Za-z0-9_]/.test(char) ? char : `\\u{${cp.toString(16)}}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// NameStartChar and NameChar of XML 1.0 fifth edition, with ":"
|
|
35
|
+
const [, NAME_START, NAME_CHAR] = /^\^\[(.+?)\]\[(.+)\]\*\$$/.exec(
|
|
36
|
+
namePatterns.name.source,
|
|
37
|
+
);
|
|
38
|
+
const SPACE = "\\u{9}\\u{a}\\u{d}\\u{20}";
|
|
39
|
+
|
|
40
|
+
/** Multi-character escapes (XSD G.4.2.4 and XML name escapes). */
|
|
41
|
+
const multiEscapes = {
|
|
42
|
+
s: { set: SPACE },
|
|
43
|
+
S: { atom: `[^${SPACE}]` },
|
|
44
|
+
d: { set: "\\p{Nd}" },
|
|
45
|
+
D: { set: "\\P{Nd}" },
|
|
46
|
+
w: { atom: "[^\\p{P}\\p{Z}\\p{C}]" },
|
|
47
|
+
W: { set: "\\p{P}\\p{Z}\\p{C}" },
|
|
48
|
+
i: { set: NAME_START },
|
|
49
|
+
I: { atom: `[^${NAME_START}]` },
|
|
50
|
+
c: { set: NAME_CHAR },
|
|
51
|
+
C: { atom: `[^${NAME_CHAR}]` },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Characters after "\" that stand for themselves (plus n, r, t). */
|
|
55
|
+
const singleEscapes = new Set([..."\\|.?*+(){}-[]^$"]);
|
|
56
|
+
const controlEscapes = { n: 0xa, r: 0xd, t: 0x9 };
|
|
57
|
+
|
|
58
|
+
const categories = new Set(
|
|
59
|
+
(
|
|
60
|
+
"L Lu Ll Lt Lm Lo M Mn Mc Me N Nd Nl No P Pc Pd Ps Pe Pi Pf Po " +
|
|
61
|
+
"Z Zs Zl Zp S Sm Sc Sk So C Cc Cf Co Cn"
|
|
62
|
+
).split(" "),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The item of `\p{name}` or `\P{name}` (the reader is after "p" or "P").
|
|
67
|
+
* @param {import("./reader.js").RegexReader} reader
|
|
68
|
+
* @param {boolean} negated - For `\P`
|
|
69
|
+
* @returns {{set?: string, atom?: string}}
|
|
70
|
+
*/
|
|
71
|
+
function categoryEscape(reader, negated) {
|
|
72
|
+
if (reader.next() !== "{") regexError("expected { after \\p");
|
|
73
|
+
let name = "";
|
|
74
|
+
while (reader.peek() !== undefined && reader.peek() !== "}") {
|
|
75
|
+
name += reader.next();
|
|
76
|
+
}
|
|
77
|
+
if (reader.next() !== "}") regexError("unterminated \\p{");
|
|
78
|
+
if (categories.has(name)) {
|
|
79
|
+
const set = `\\${negated ? "P" : "p"}{${name}}`;
|
|
80
|
+
// with the i flag a category still matches by its own case
|
|
81
|
+
return reader.caseless ? { atom: `(?-i:[${set}])` } : { set };
|
|
82
|
+
}
|
|
83
|
+
const ranges = name.startsWith("Is") && blockRanges(name.slice(2));
|
|
84
|
+
if (!ranges) regexError(`unknown category or block \\p{${name}}`);
|
|
85
|
+
const set = ranges
|
|
86
|
+
.map(([from, to]) => `${jsChar(from)}-${jsChar(to)}`)
|
|
87
|
+
.join("");
|
|
88
|
+
return negated ? { atom: `[^${set}]` } : { set };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Parses an escape after its backslash (not a back-reference).
|
|
93
|
+
* @param {import("./reader.js").RegexReader} reader
|
|
94
|
+
* @returns {{set?: string, atom?: string, char?: number}}
|
|
95
|
+
* @throws {XPathError} FORX0002 for unknown escapes
|
|
96
|
+
*/
|
|
97
|
+
export function parseEscape(reader) {
|
|
98
|
+
const c = reader.next();
|
|
99
|
+
if (c === undefined) regexError("trailing backslash");
|
|
100
|
+
if (singleEscapes.has(c)) {
|
|
101
|
+
return { char: c.codePointAt(0), set: jsChar(c.codePointAt(0)) };
|
|
102
|
+
}
|
|
103
|
+
if (c in controlEscapes) {
|
|
104
|
+
return { char: controlEscapes[c], set: jsChar(controlEscapes[c]) };
|
|
105
|
+
}
|
|
106
|
+
if (Object.hasOwn(multiEscapes, c)) return multiEscapes[c];
|
|
107
|
+
if (c === "p" || c === "P") return categoryEscape(reader, c === "P");
|
|
108
|
+
return regexError(`unknown escape \\${c}`);
|
|
109
|
+
}
|