@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.
Files changed (286) hide show
  1. package/LICENSE.md +28 -0
  2. package/README.md +98 -2
  3. package/package.json +43 -4
  4. package/src/errors.js +20 -0
  5. package/src/functions/aggregates.js +144 -0
  6. package/src/functions/arraySort.js +33 -0
  7. package/src/functions/arrays.js +181 -0
  8. package/src/functions/attributeTypes.js +58 -0
  9. package/src/functions/baseUris.js +82 -0
  10. package/src/functions/collations.js +127 -0
  11. package/src/functions/collections.js +84 -0
  12. package/src/functions/context.js +83 -0
  13. package/src/functions/core.js +167 -0
  14. package/src/functions/datetime.js +203 -0
  15. package/src/functions/deepEqual.js +132 -0
  16. package/src/functions/diagnostics.js +88 -0
  17. package/src/functions/focus.js +34 -0
  18. package/src/functions/format/dateComponents.js +176 -0
  19. package/src/functions/format/datePicture.js +115 -0
  20. package/src/functions/format/decimalFormat.js +65 -0
  21. package/src/functions/format/digitPattern.js +145 -0
  22. package/src/functions/format/formatDateTime.js +204 -0
  23. package/src/functions/format/formatInteger.js +109 -0
  24. package/src/functions/format/formatNumber.js +165 -0
  25. package/src/functions/format/fractionFormat.js +80 -0
  26. package/src/functions/format/germanWords.js +89 -0
  27. package/src/functions/format/numberLanguages.js +41 -0
  28. package/src/functions/format/numberPicture.js +202 -0
  29. package/src/functions/format/numberWords.js +155 -0
  30. package/src/functions/format/place.js +74 -0
  31. package/src/functions/format/timezoneFormat.js +89 -0
  32. package/src/functions/higherOrder.js +106 -0
  33. package/src/functions/ids.js +127 -0
  34. package/src/functions/json/index.js +17 -0
  35. package/src/functions/json/jsonChars.js +110 -0
  36. package/src/functions/json/jsonParser.js +203 -0
  37. package/src/functions/json/jsonStrings.js +116 -0
  38. package/src/functions/json/jsonToXml.js +116 -0
  39. package/src/functions/json/parseJson.js +119 -0
  40. package/src/functions/json/xmlRepresentation.js +103 -0
  41. package/src/functions/json/xmlToJson.js +147 -0
  42. package/src/functions/library.js +47 -0
  43. package/src/functions/mapArrayEqual.js +41 -0
  44. package/src/functions/maps.js +152 -0
  45. package/src/functions/math.js +73 -0
  46. package/src/functions/nodePaths.js +181 -0
  47. package/src/functions/nodes.js +199 -0
  48. package/src/functions/numeric.js +148 -0
  49. package/src/functions/options.js +58 -0
  50. package/src/functions/parseIetfDate.js +104 -0
  51. package/src/functions/parseXml.js +77 -0
  52. package/src/functions/qnames.js +68 -0
  53. package/src/functions/random.js +128 -0
  54. package/src/functions/regex/analyzeString.js +144 -0
  55. package/src/functions/regex/charClass.js +112 -0
  56. package/src/functions/regex/compile.js +81 -0
  57. package/src/functions/regex/escapes.js +109 -0
  58. package/src/functions/regex/parser.js +169 -0
  59. package/src/functions/regex/reader.js +37 -0
  60. package/src/functions/regex/regexFunctions.js +140 -0
  61. package/src/functions/regex/translate.js +102 -0
  62. package/src/functions/regex/unicodeBlocks.js +194 -0
  63. package/src/functions/registry.js +140 -0
  64. package/src/functions/sequences.js +173 -0
  65. package/src/functions/serializeFunction.js +53 -0
  66. package/src/functions/signatures.js +105 -0
  67. package/src/functions/sort.js +102 -0
  68. package/src/functions/stringMatch.js +45 -0
  69. package/src/functions/strings.js +194 -0
  70. package/src/functions/stringsCore.js +66 -0
  71. package/src/functions/support.js +106 -0
  72. package/src/functions/textDecoding.js +117 -0
  73. package/src/functions/ucaCollation.js +127 -0
  74. package/src/functions/unparsedText.js +71 -0
  75. package/src/functions/uri.js +166 -0
  76. package/src/functions/xqueryModule.js +24 -0
  77. package/src/index.js +38 -0
  78. package/src/items/array.js +84 -0
  79. package/src/items/function.js +45 -0
  80. package/src/items/map.js +120 -0
  81. package/src/items/mapKey.js +82 -0
  82. package/src/serialize/index.js +129 -0
  83. package/src/serialize/markup/content.js +74 -0
  84. package/src/serialize/markup/dialect.js +178 -0
  85. package/src/serialize/markup/htmlData.js +70 -0
  86. package/src/serialize/markup/namespaces.js +145 -0
  87. package/src/serialize/markup/prolog.js +91 -0
  88. package/src/serialize/markup/writer.js +183 -0
  89. package/src/serialize/methods/adaptive.js +131 -0
  90. package/src/serialize/methods/json.js +158 -0
  91. package/src/serialize/methods/text.js +43 -0
  92. package/src/serialize/output/buffer.js +48 -0
  93. package/src/serialize/output/encoding.js +146 -0
  94. package/src/serialize/output/expander.js +204 -0
  95. package/src/serialize/params/fromElement.js +141 -0
  96. package/src/serialize/params/fromMap.js +128 -0
  97. package/src/serialize/params/names.js +69 -0
  98. package/src/serialize/params/settings.js +153 -0
  99. package/src/serialize/params/values.js +163 -0
  100. package/src/serialize/sequence.js +111 -0
  101. package/src/xdm/arithmetic.js +155 -0
  102. package/src/xdm/atomic.js +142 -0
  103. package/src/xdm/binary.js +92 -0
  104. package/src/xdm/calendar.js +71 -0
  105. package/src/xdm/cast.js +176 -0
  106. package/src/xdm/compare.js +192 -0
  107. package/src/xdm/datetime.js +206 -0
  108. package/src/xdm/decimal.js +200 -0
  109. package/src/xdm/duration.js +128 -0
  110. package/src/xdm/generalCompare.js +152 -0
  111. package/src/xdm/index.js +47 -0
  112. package/src/xdm/lexical.js +126 -0
  113. package/src/xdm/nodes.js +170 -0
  114. package/src/xdm/numeric.js +122 -0
  115. package/src/xdm/qname.js +65 -0
  116. package/src/xdm/strings.js +52 -0
  117. package/src/xdm/temporalArithmetic.js +180 -0
  118. package/src/xdm/timeline.js +68 -0
  119. package/src/xdm/types.js +178 -0
  120. package/src/xpath/eval/atomics.js +45 -0
  121. package/src/xpath/eval/axes.js +212 -0
  122. package/src/xpath/eval/calls.js +168 -0
  123. package/src/xpath/eval/coercion.js +146 -0
  124. package/src/xpath/eval/compiler.js +71 -0
  125. package/src/xpath/eval/constructors.js +129 -0
  126. package/src/xpath/eval/decimalFormats.js +93 -0
  127. package/src/xpath/eval/descendantMemo.js +77 -0
  128. package/src/xpath/eval/documentOrder.js +142 -0
  129. package/src/xpath/eval/domNodes.js +174 -0
  130. package/src/xpath/eval/dynamicCall.js +27 -0
  131. package/src/xpath/eval/dynamicContext.js +161 -0
  132. package/src/xpath/eval/flow.js +115 -0
  133. package/src/xpath/eval/functionItems.js +194 -0
  134. package/src/xpath/eval/groupedChildren.js +88 -0
  135. package/src/xpath/eval/inline.js +71 -0
  136. package/src/xpath/eval/namespaceNodes.js +90 -0
  137. package/src/xpath/eval/nodeTests.js +164 -0
  138. package/src/xpath/eval/operators.js +190 -0
  139. package/src/xpath/eval/paths.js +163 -0
  140. package/src/xpath/eval/positional.js +81 -0
  141. package/src/xpath/eval/predicates.js +56 -0
  142. package/src/xpath/eval/primary.js +99 -0
  143. package/src/xpath/eval/resources.js +149 -0
  144. package/src/xpath/eval/scope.js +108 -0
  145. package/src/xpath/eval/sequenceType.js +193 -0
  146. package/src/xpath/eval/staticContext.js +142 -0
  147. package/src/xpath/eval/steps.js +98 -0
  148. package/src/xpath/eval/subtype.js +154 -0
  149. package/src/xpath/eval/typeExprs.js +147 -0
  150. package/src/xpath/eval/typeNames.js +101 -0
  151. package/src/xpath/eval/uris.js +21 -0
  152. package/src/xpath/eval/values.js +64 -0
  153. package/src/xpath/index.js +217 -0
  154. package/src/xpath/syntax/ast.js +130 -0
  155. package/src/xpath/syntax/chars.js +58 -0
  156. package/src/xpath/syntax/constructors.js +144 -0
  157. package/src/xpath/syntax/expressions.js +107 -0
  158. package/src/xpath/syntax/index.js +44 -0
  159. package/src/xpath/syntax/kindTests.js +134 -0
  160. package/src/xpath/syntax/lexer.js +220 -0
  161. package/src/xpath/syntax/operators.js +111 -0
  162. package/src/xpath/syntax/paths.js +102 -0
  163. package/src/xpath/syntax/postfix.js +91 -0
  164. package/src/xpath/syntax/primaries.js +149 -0
  165. package/src/xpath/syntax/steps.js +146 -0
  166. package/src/xpath/syntax/syntaxError.js +31 -0
  167. package/src/xpath/syntax/tokenStream.js +163 -0
  168. package/src/xpath/syntax/typeAst.js +76 -0
  169. package/src/xpath/syntax/typeOperators.js +127 -0
  170. package/src/xpath/syntax/types.js +158 -0
  171. package/src/xslt/api.js +153 -0
  172. package/src/xslt/compiler/acceptance.js +178 -0
  173. package/src/xslt/compiler/accumulatorDecl.js +146 -0
  174. package/src/xslt/compiler/attributeValues.js +174 -0
  175. package/src/xslt/compiler/attributes.js +211 -0
  176. package/src/xslt/compiler/avt.js +103 -0
  177. package/src/xslt/compiler/body.js +154 -0
  178. package/src/xslt/compiler/children.js +105 -0
  179. package/src/xslt/compiler/compatibility.js +130 -0
  180. package/src/xslt/compiler/components.js +179 -0
  181. package/src/xslt/compiler/contextItem.js +95 -0
  182. package/src/xslt/compiler/decimalFormats.js +132 -0
  183. package/src/xslt/compiler/declarationKinds.js +82 -0
  184. package/src/xslt/compiler/elementInfo.js +246 -0
  185. package/src/xslt/compiler/expressions.js +175 -0
  186. package/src/xslt/compiler/globalContextItem.js +92 -0
  187. package/src/xslt/compiler/globals.js +209 -0
  188. package/src/xslt/compiler/linker.js +134 -0
  189. package/src/xslt/compiler/miscDecl.js +171 -0
  190. package/src/xslt/compiler/modeDecl.js +88 -0
  191. package/src/xslt/compiler/moduleLoading.js +111 -0
  192. package/src/xslt/compiler/modules.js +158 -0
  193. package/src/xslt/compiler/outputDecl.js +209 -0
  194. package/src/xslt/compiler/overrideLinks.js +141 -0
  195. package/src/xslt/compiler/packageChecks.js +84 -0
  196. package/src/xslt/compiler/packageRegistry.js +95 -0
  197. package/src/xslt/compiler/packageTable.js +181 -0
  198. package/src/xslt/compiler/packageVersions.js +143 -0
  199. package/src/xslt/compiler/packages.js +138 -0
  200. package/src/xslt/compiler/registry.js +234 -0
  201. package/src/xslt/compiler/staticStage.js +197 -0
  202. package/src/xslt/compiler/staticVariables.js +93 -0
  203. package/src/xslt/compiler/stylesheet.js +205 -0
  204. package/src/xslt/compiler/tailPosition.js +74 -0
  205. package/src/xslt/compiler/templatesDecl.js +180 -0
  206. package/src/xslt/compiler/useAccumulators.js +76 -0
  207. package/src/xslt/compiler/useWhen.js +94 -0
  208. package/src/xslt/compiler/visibility.js +160 -0
  209. package/src/xslt/index.js +11 -0
  210. package/src/xslt/instructions/analyzeString.js +137 -0
  211. package/src/xslt/instructions/assert.js +72 -0
  212. package/src/xslt/instructions/attributeSets.js +115 -0
  213. package/src/xslt/instructions/conditional.js +99 -0
  214. package/src/xslt/instructions/control.js +123 -0
  215. package/src/xslt/instructions/copy.js +151 -0
  216. package/src/xslt/instructions/elements.js +161 -0
  217. package/src/xslt/instructions/evaluate.js +189 -0
  218. package/src/xslt/instructions/fork.js +35 -0
  219. package/src/xslt/instructions/grouping.js +106 -0
  220. package/src/xslt/instructions/index.js +94 -0
  221. package/src/xslt/instructions/iterate.js +182 -0
  222. package/src/xslt/instructions/literal.js +142 -0
  223. package/src/xslt/instructions/maps.js +72 -0
  224. package/src/xslt/instructions/merge.js +190 -0
  225. package/src/xslt/instructions/number.js +205 -0
  226. package/src/xslt/instructions/output.js +102 -0
  227. package/src/xslt/instructions/params.js +110 -0
  228. package/src/xslt/instructions/resultDocument.js +143 -0
  229. package/src/xslt/instructions/sort.js +114 -0
  230. package/src/xslt/instructions/sourceDocument.js +73 -0
  231. package/src/xslt/instructions/templates.js +153 -0
  232. package/src/xslt/instructions/text.js +151 -0
  233. package/src/xslt/instructions/try.js +186 -0
  234. package/src/xslt/instructions/variables.js +119 -0
  235. package/src/xslt/names.js +204 -0
  236. package/src/xslt/patterns/axisOrigins.js +121 -0
  237. package/src/xslt/patterns/compile.js +151 -0
  238. package/src/xslt/patterns/general.js +132 -0
  239. package/src/xslt/patterns/grammar.js +190 -0
  240. package/src/xslt/patterns/modes.js +153 -0
  241. package/src/xslt/patterns/paths.js +130 -0
  242. package/src/xslt/patterns/patternChecks.js +29 -0
  243. package/src/xslt/patterns/steps.js +162 -0
  244. package/src/xslt/processor.js +178 -0
  245. package/src/xslt/runtime/accumulatorFunctions.js +48 -0
  246. package/src/xslt/runtime/accumulators.js +174 -0
  247. package/src/xslt/runtime/apply.js +153 -0
  248. package/src/xslt/runtime/atomicKeys.js +36 -0
  249. package/src/xslt/runtime/availability.js +162 -0
  250. package/src/xslt/runtime/baseUri.js +64 -0
  251. package/src/xslt/runtime/compilerHook.js +27 -0
  252. package/src/xslt/runtime/conditional.js +110 -0
  253. package/src/xslt/runtime/context.js +106 -0
  254. package/src/xslt/runtime/copy.js +123 -0
  255. package/src/xslt/runtime/copyFunctions.js +71 -0
  256. package/src/xslt/runtime/copyOrigins.js +60 -0
  257. package/src/xslt/runtime/defaultMethod.js +42 -0
  258. package/src/xslt/runtime/documentContent.js +93 -0
  259. package/src/xslt/runtime/dynamicXPath.js +90 -0
  260. package/src/xslt/runtime/functionCache.js +65 -0
  261. package/src/xslt/runtime/functionCall.js +57 -0
  262. package/src/xslt/runtime/functions.js +191 -0
  263. package/src/xslt/runtime/groups.js +108 -0
  264. package/src/xslt/runtime/invocation.js +156 -0
  265. package/src/xslt/runtime/iteration.js +107 -0
  266. package/src/xslt/runtime/keys.js +141 -0
  267. package/src/xslt/runtime/lazy.js +97 -0
  268. package/src/xslt/runtime/locations.js +86 -0
  269. package/src/xslt/runtime/machine.js +137 -0
  270. package/src/xslt/runtime/merging.js +167 -0
  271. package/src/xslt/runtime/namespaceScope.js +141 -0
  272. package/src/xslt/runtime/numberFormat.js +89 -0
  273. package/src/xslt/runtime/numbering.js +168 -0
  274. package/src/xslt/runtime/params.js +59 -0
  275. package/src/xslt/runtime/properties.js +103 -0
  276. package/src/xslt/runtime/rawResults.js +82 -0
  277. package/src/xslt/runtime/sequenceReceiver.js +102 -0
  278. package/src/xslt/runtime/snapshot.js +66 -0
  279. package/src/xslt/runtime/sortKeys.js +119 -0
  280. package/src/xslt/runtime/streamAvailable.js +60 -0
  281. package/src/xslt/runtime/strip.js +102 -0
  282. package/src/xslt/runtime/transformFunction.js +169 -0
  283. package/src/xslt/runtime/transformation.js +168 -0
  284. package/src/xslt/runtime/transformationContext.js +85 -0
  285. package/src/xslt/runtime/treeReceiver.js +194 -0
  286. 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
+ }