@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,194 @@
1
+ /**
2
+ * Functions on strings (F&O 3.1 sections 5.2, 5.3 and 5.4): codepoints,
3
+ * comparison, substring, normalization, case mapping, translate and
4
+ * contains-token. Strings are handled as sequences of Unicode codepoints,
5
+ * not UTF-16 code units.
6
+ *
7
+ * @module @tradik/xslt3/functions/strings
8
+ */
9
+
10
+ import { XPathError } from "../errors.js";
11
+ import { normalizeWhitespace } from "../xdm/strings.js";
12
+ import { collationArg } from "./collations.js";
13
+ import {
14
+ booleanItem,
15
+ contextString,
16
+ define,
17
+ integerItem,
18
+ isXmlChar,
19
+ stringArg,
20
+ stringItem,
21
+ } from "./support.js";
22
+
23
+ /**
24
+ * fn:round on a double: nearest integer, halves toward positive infinity.
25
+ * @param {number} x
26
+ * @returns {number}
27
+ */
28
+ export function roundHalfUp(x) {
29
+ const floor = Math.floor(x);
30
+ return x - floor >= 0.5 ? floor + 1 : floor;
31
+ }
32
+
33
+ /**
34
+ * The codepoints p (1-based) of a sequence of `length` codepoints with
35
+ * `round(start) <= p < round(start) + round(size)`, the selection rule of
36
+ * fn:substring and fn:subsequence (comparisons with NaN are false).
37
+ * @param {number} length
38
+ * @param {number} start
39
+ * @param {number} [size] - All remaining when undefined
40
+ * @returns {[number, number]} 0-based [from, to) slice bounds, from >= to
41
+ * when nothing is selected
42
+ */
43
+ export function selectRange(length, start, size) {
44
+ const first = roundHalfUp(start);
45
+ const end = size === undefined ? Infinity : first + roundHalfUp(size);
46
+ const from = Math.max(first, 1);
47
+ const to = Math.min(end, length + 1);
48
+ return from < to ? [from - 1, to - 1] : [0, 0];
49
+ }
50
+
51
+ /** @param {string} s @returns {string} XML whitespace collapsed */
52
+ const collapse = (s) => normalizeWhitespace(s, "collapse");
53
+
54
+ /** @param {string} s @returns {string[]} the codepoints as strings */
55
+ const codepoints = (s) => Array.from(s);
56
+
57
+ const NORMALIZATION_FORMS = new Set(["NFC", "NFD", "NFKC", "NFKD"]);
58
+
59
+ /**
60
+ * fn:normalize-unicode.
61
+ * @param {string} text
62
+ * @param {string} form - Normalization form name (any case, trimmed)
63
+ * @returns {string}
64
+ * @throws {XPathError} FOCH0003 for another form than NFC, NFD, NFKC and
65
+ * NFKD (FULLY-NORMALIZED is not supported: no normalization of a string
66
+ * that starts with a composing character yields a fully-normalized one)
67
+ */
68
+ function normalizeUnicode(text, form) {
69
+ const name = collapse(form).toUpperCase();
70
+ if (name === "") return text;
71
+ if (!NORMALIZATION_FORMS.has(name)) {
72
+ throw new XPathError("FOCH0003", `Unsupported normalization form ${form}`);
73
+ }
74
+ return text.normalize(name);
75
+ }
76
+
77
+ /**
78
+ * fn:translate.
79
+ * @param {string} text
80
+ * @param {string} mapString
81
+ * @param {string} transString
82
+ * @returns {string}
83
+ */
84
+ function translate(text, mapString, transString) {
85
+ const map = new Map();
86
+ const replacements = codepoints(transString);
87
+ codepoints(mapString).forEach((char, i) => {
88
+ if (!map.has(char)) map.set(char, replacements[i] ?? "");
89
+ });
90
+ return codepoints(text)
91
+ .map((char) => map.get(char) ?? char)
92
+ .join("");
93
+ }
94
+
95
+ /**
96
+ * fn:compare.
97
+ * @param {Array<*>[]} args - [$a, $b, $collation?]
98
+ * @param {object} context
99
+ * @returns {Array<*>}
100
+ */
101
+ function compare([a, b, collation], context) {
102
+ const { compare: order } = collationArg(collation, context);
103
+ if (a.length === 0 || b.length === 0) return [];
104
+ return [integerItem(order(a[0].value, b[0].value))];
105
+ }
106
+
107
+ /**
108
+ * fn:contains-token.
109
+ * @param {Array<*>[]} args - [$input, $token, $collation?]
110
+ * @param {object} context
111
+ * @returns {Array<*>}
112
+ */
113
+ function containsToken([input, [token], collation], context) {
114
+ const { compare: order } = collationArg(collation, context);
115
+ const wanted = collapse(token.value);
116
+ if (wanted === "") return [booleanItem(false)];
117
+ const found = input.some((item) =>
118
+ collapse(item.value)
119
+ .split(" ")
120
+ .some((part) => order(part, wanted) === 0),
121
+ );
122
+ return [booleanItem(found)];
123
+ }
124
+
125
+ /**
126
+ * fn:substring.
127
+ * @param {Array<*>[]} args - [$source, $start, $length?]
128
+ * @returns {Array<*>}
129
+ */
130
+ function substring([source, [start], length]) {
131
+ const chars = codepoints(stringArg(source));
132
+ const size = length === undefined ? undefined : length[0].value;
133
+ const [from, to] = selectRange(chars.length, start.value, size);
134
+ return [stringItem(chars.slice(from, to).join(""))];
135
+ }
136
+
137
+ const S = "xs:string";
138
+ const S_OPT = "xs:string?";
139
+
140
+ /** @type {import("./support.js").FunctionDefinition[]} */
141
+ export const stringFunctions = [
142
+ define("codepoints-to-string", ["xs:integer*"], S, ([cps]) => [
143
+ stringItem(
144
+ cps
145
+ .map(({ value }) => {
146
+ const cp = Number(value);
147
+ if (!isXmlChar(cp)) {
148
+ throw new XPathError("FOCH0001", `Invalid codepoint ${value}`);
149
+ }
150
+ return String.fromCodePoint(cp);
151
+ })
152
+ .join(""),
153
+ ),
154
+ ]),
155
+ define("string-to-codepoints", [S_OPT], "xs:integer*", ([s]) =>
156
+ codepoints(stringArg(s)).map((c) => integerItem(c.codePointAt(0))),
157
+ ),
158
+ define("compare", [S_OPT, S_OPT], "xs:integer?", compare),
159
+ define("compare", [S_OPT, S_OPT, S], "xs:integer?", compare),
160
+ define("codepoint-equal", [S_OPT, S_OPT], "xs:boolean?", ([a, b]) =>
161
+ a.length === 0 || b.length === 0
162
+ ? []
163
+ : [booleanItem(a[0].value === b[0].value)],
164
+ ),
165
+ define("substring", [S_OPT, "xs:double"], S, substring),
166
+ define("substring", [S_OPT, "xs:double", "xs:double"], S, substring),
167
+ define(
168
+ "normalize-space",
169
+ [],
170
+ S,
171
+ (_args, context) => [stringItem(collapse(contextString(context)))],
172
+ { focus: true },
173
+ ),
174
+ define("normalize-space", [S_OPT], S, ([s]) => [
175
+ stringItem(collapse(stringArg(s))),
176
+ ]),
177
+ define("normalize-unicode", [S_OPT], S, ([s]) => [
178
+ stringItem(normalizeUnicode(stringArg(s), "NFC")),
179
+ ]),
180
+ define("normalize-unicode", [S_OPT, S], S, ([s, [form]]) => [
181
+ stringItem(normalizeUnicode(stringArg(s), form.value)),
182
+ ]),
183
+ define("upper-case", [S_OPT], S, ([s]) => [
184
+ stringItem(stringArg(s).toUpperCase()),
185
+ ]),
186
+ define("lower-case", [S_OPT], S, ([s]) => [
187
+ stringItem(stringArg(s).toLowerCase()),
188
+ ]),
189
+ define("translate", [S_OPT, S, S], S, ([s, [map], [trans]]) => [
190
+ stringItem(translate(stringArg(s), map.value, trans.value)),
191
+ ]),
192
+ define("contains-token", ["xs:string*", S], "xs:boolean", containsToken),
193
+ define("contains-token", ["xs:string*", S, S], "xs:boolean", containsToken),
194
+ ];
@@ -0,0 +1,66 @@
1
+ /**
2
+ * fn:concat, fn:string-join and fn:string-length (F&O 3.1 5.4.1 to 5.4.4).
3
+ *
4
+ * The evaluator's core library may define these functions itself; this
5
+ * array exists so they are available either way. Register it only for the
6
+ * names the core library leaves out, to avoid duplicate definitions.
7
+ *
8
+ * @module @tradik/xslt3/functions/stringsCore
9
+ */
10
+
11
+ import { canonicalString } from "../xdm/lexical.js";
12
+ import {
13
+ contextString,
14
+ define,
15
+ integerItem,
16
+ stringArg,
17
+ stringItem,
18
+ } from "./support.js";
19
+
20
+ /** @param {Array<*>} sequence @returns {string} "" or the canonical string */
21
+ const optionalString = (sequence) =>
22
+ sequence.length === 0 ? "" : canonicalString(sequence[0]);
23
+
24
+ /** @param {string} s @returns {number} length in codepoints */
25
+ const codepointLength = (s) => {
26
+ let length = 0;
27
+ for (const _char of s) length++;
28
+ return length;
29
+ };
30
+
31
+ /**
32
+ * fn:string-join.
33
+ * @param {Array<*>[]} args - [$arg1, $arg2?]
34
+ * @returns {Array<*>}
35
+ */
36
+ const stringJoin = ([items, separator = []]) => [
37
+ stringItem(items.map(canonicalString).join(stringArg(separator))),
38
+ ];
39
+
40
+ /** @type {import("./support.js").FunctionDefinition[]} */
41
+ export const coreStringFunctions = [
42
+ define(
43
+ "concat",
44
+ ["xs:anyAtomicType?", "xs:anyAtomicType?"],
45
+ "xs:string",
46
+ (args) => [stringItem(args.map(optionalString).join(""))],
47
+ { variadic: true },
48
+ ),
49
+ define("string-join", ["xs:anyAtomicType*"], "xs:string", stringJoin),
50
+ define(
51
+ "string-join",
52
+ ["xs:anyAtomicType*", "xs:string"],
53
+ "xs:string",
54
+ stringJoin,
55
+ ),
56
+ define(
57
+ "string-length",
58
+ [],
59
+ "xs:integer",
60
+ (_args, context) => [integerItem(codepointLength(contextString(context)))],
61
+ { focus: true },
62
+ ),
63
+ define("string-length", ["xs:string?"], "xs:integer", ([s]) => [
64
+ integerItem(codepointLength(stringArg(s))),
65
+ ]),
66
+ ];
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Shared helpers of the function modules: namespaces, a compact way to
3
+ * declare {@link FunctionDefinition}s and constructors of the atomic
4
+ * results the functions return.
5
+ *
6
+ * @module @tradik/xslt3/functions/support
7
+ */
8
+
9
+ import { XPathError } from "../errors.js";
10
+ import { AtomicValue } from "../xdm/atomic.js";
11
+ import { Decimal } from "../xdm/decimal.js";
12
+ import { stringValue } from "../xdm/nodes.js";
13
+ import { types } from "../xdm/types.js";
14
+
15
+ /** Namespace of the standard functions (prefix fn). */
16
+ export const FN_NAMESPACE = "http://www.w3.org/2005/xpath-functions";
17
+ /** Namespace of the math functions (prefix math). */
18
+ export const MATH_NAMESPACE = "http://www.w3.org/2005/xpath-functions/math";
19
+
20
+ /**
21
+ * A built-in function of one arity, as consumed by the function registry.
22
+ * @typedef {object} FunctionDefinition
23
+ * @property {string} [namespace] - Defaults to {@link FN_NAMESPACE}
24
+ * @property {string} local - Local name, e.g. "upper-case"
25
+ * @property {string[]} params - Parameter sequence types, e.g. ["xs:string?"]
26
+ * @property {string} returns - Result sequence type
27
+ * @property {boolean} [variadic] - The last parameter repeats
28
+ * @property {boolean} [focus] - Needs the focus (context item, position, size)
29
+ * @property {(args: Array<Array<*>>, context: object) => Array<*>} impl
30
+ */
31
+
32
+ /**
33
+ * Declares a function of the fn namespace.
34
+ * @param {string} local
35
+ * @param {string[]} params
36
+ * @param {string} returns
37
+ * @param {FunctionDefinition["impl"]} impl
38
+ * @param {Partial<FunctionDefinition>} [extra] - namespace, focus, variadic
39
+ * @returns {FunctionDefinition}
40
+ */
41
+ export function define(local, params, returns, impl, extra = {}) {
42
+ return Object.freeze({
43
+ namespace: FN_NAMESPACE,
44
+ local,
45
+ params,
46
+ returns,
47
+ impl,
48
+ ...extra,
49
+ });
50
+ }
51
+
52
+ /** @param {string} s @returns {AtomicValue} an xs:string */
53
+ export const stringItem = (s) => new AtomicValue(types.string, s);
54
+ /** @param {boolean} b @returns {AtomicValue} an xs:boolean */
55
+ export const booleanItem = (b) => new AtomicValue(types.boolean, b);
56
+ /** @param {number} n @returns {AtomicValue} an xs:double */
57
+ export const doubleItem = (n) => new AtomicValue(types.double, n);
58
+ /** @param {bigint|number} n @returns {AtomicValue} an xs:integer */
59
+ export const integerItem = (n) => new AtomicValue(types.integer, BigInt(n));
60
+ /** @param {Decimal} d @returns {AtomicValue} an xs:decimal */
61
+ export const decimalItem = (d) => new AtomicValue(types.decimal, d);
62
+
63
+ /**
64
+ * @param {number} cp
65
+ * @returns {boolean} whether the codepoint is an XML 1.0 Char
66
+ */
67
+ export const isXmlChar = (cp) =>
68
+ cp === 0x9 ||
69
+ cp === 0xa ||
70
+ cp === 0xd ||
71
+ (cp >= 0x20 && cp <= 0xd7ff) ||
72
+ (cp >= 0xe000 && cp <= 0xfffd) ||
73
+ (cp >= 0x10000 && cp <= 0x10ffff);
74
+
75
+ /**
76
+ * The string of an optional xs:string argument ("" for the empty sequence).
77
+ * @param {Array<AtomicValue>} sequence
78
+ * @returns {string}
79
+ */
80
+ export const stringArg = (sequence) => sequence[0]?.value ?? "";
81
+
82
+ /**
83
+ * Numeric value of an atomic number as a JS double.
84
+ * @param {AtomicValue} item - xs:integer, xs:decimal, xs:float or xs:double
85
+ * @returns {number}
86
+ */
87
+ export function toDouble(item) {
88
+ const { value } = item;
89
+ if (typeof value === "number") return value;
90
+ return value instanceof Decimal ? value.toNumber() : Number(value);
91
+ }
92
+
93
+ /**
94
+ * String value of the context item, for the zero-argument forms that
95
+ * default to it (fn:normalize-space(), fn:string-length()).
96
+ * @param {{contextItem?: *}} context
97
+ * @returns {string}
98
+ * @throws {XPathError} XPDY0002 when the context item is absent,
99
+ * FOTY0014 for function items
100
+ */
101
+ export function contextString(context) {
102
+ if (context.contextItem === undefined || context.contextItem === null) {
103
+ throw new XPathError("XPDY0002", "The context item is absent");
104
+ }
105
+ return stringValue(context.contextItem);
106
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Decoding of the text resources read by fn:unparsed-text and its family
3
+ * (F&O 3.1 section 14.6.1): the encoding comes from a byte order mark,
4
+ * else from external information (the loader's `encoding`), else from the
5
+ * XML declaration of an XML media type, else from the `$encoding`
6
+ * argument, else UTF-8.
7
+ *
8
+ * @module @tradik/xslt3/functions/textDecoding
9
+ */
10
+
11
+ import { XPathError } from "../errors.js";
12
+
13
+ /** A character that is not an XML 1.0 Char (lone surrogates included). */
14
+ const NON_XML = /[^\t\n\r\x20-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/u;
15
+
16
+ /**
17
+ * What a text loader returns: the text itself, its bytes, or either with
18
+ * external encoding information and a media type.
19
+ * @typedef {string|Uint8Array|ArrayBuffer|{content: string|Uint8Array|ArrayBuffer, encoding?: string, mediaType?: string}} TextResource
20
+ */
21
+
22
+ /**
23
+ * The encoding a byte order mark announces.
24
+ * @param {Uint8Array} bytes
25
+ * @returns {string|null}
26
+ */
27
+ function bomEncoding(bytes) {
28
+ if (bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) {
29
+ return "utf-8";
30
+ }
31
+ if (bytes[0] === 0xfe && bytes[1] === 0xff) return "utf-16be";
32
+ if (bytes[0] === 0xff && bytes[1] === 0xfe) return "utf-16le";
33
+ return null;
34
+ }
35
+
36
+ /**
37
+ * The encoding of an XML document without byte order mark: UTF-16 by its
38
+ * first bytes, else the encoding declaration, else UTF-8.
39
+ * @param {Uint8Array} bytes
40
+ * @returns {string}
41
+ */
42
+ function xmlEncoding(bytes) {
43
+ if (bytes[0] === 0x3c && bytes[1] === 0) return "utf-16le";
44
+ if (bytes[0] === 0 && bytes[1] === 0x3c) return "utf-16be";
45
+ const head = String.fromCharCode(...bytes.subarray(0, 200));
46
+ return (
47
+ /^<\?xml[^>]*?encoding\s*=\s*["']([^"']*)["']/.exec(head)?.[1] ?? "utf-8"
48
+ );
49
+ }
50
+
51
+ /** @param {string} [type] @returns {boolean} whether it is an XML media type */
52
+ const isXmlMediaType = (type) =>
53
+ /^(?:text|application)\/(?:[\w.-]+\+)?xml\b/i.test(type ?? "");
54
+
55
+ /**
56
+ * A fatal TextDecoder; "utf-16" without byte order mark is big-endian.
57
+ * @param {string} label
58
+ * @returns {TextDecoder}
59
+ * @throws {XPathError} FOUT1190 for an unsupported encoding
60
+ */
61
+ export function textDecoder(label) {
62
+ const name = /^utf-?16$/i.test(label.trim()) ? "utf-16be" : label;
63
+ try {
64
+ return new globalThis.TextDecoder(name, { fatal: true });
65
+ } catch (error) {
66
+ throw new XPathError("FOUT1190", `Unsupported encoding ${label}`, {
67
+ cause: error,
68
+ });
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Decodes a text resource and checks its characters.
74
+ * @param {TextResource} resource
75
+ * @param {string} [encoding] - The `$encoding` argument
76
+ * @returns {string} the text, without byte order mark
77
+ * @throws {XPathError} FOUT1190 for an unsupported encoding, bytes that
78
+ * do not decode or characters that are not XML characters; FOUT1200
79
+ * when UTF-8, the encoding assumed for want of any information, fails
80
+ */
81
+ export function decodeText(resource, encoding) {
82
+ const requested = encoding === undefined ? null : textDecoder(encoding);
83
+ const plain =
84
+ typeof resource === "string" ||
85
+ resource instanceof Uint8Array ||
86
+ resource instanceof ArrayBuffer;
87
+ const {
88
+ content,
89
+ encoding: external,
90
+ mediaType,
91
+ } = plain ? { content: resource } : resource;
92
+ let text;
93
+ if (typeof content === "string") {
94
+ text = content;
95
+ } else {
96
+ const bytes = new Uint8Array(content);
97
+ const label =
98
+ bomEncoding(bytes) ??
99
+ external ??
100
+ (isXmlMediaType(mediaType) ? xmlEncoding(bytes) : null);
101
+ const decoder = label ? textDecoder(label) : requested;
102
+ try {
103
+ text = (decoder ?? textDecoder("utf-8")).decode(bytes);
104
+ } catch (error) {
105
+ throw new XPathError(
106
+ decoder ? "FOUT1190" : "FOUT1200",
107
+ `The resource is not ${decoder?.encoding ?? "utf-8"} text`,
108
+ { cause: error },
109
+ );
110
+ }
111
+ }
112
+ text = text.replace(/^\uFEFF/, "");
113
+ if (NON_XML.test(text)) {
114
+ throw new XPathError("FOUT1190", "The resource has a non-XML character");
115
+ }
116
+ return text;
117
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * The UCA collation family (F&O 3.1 section 5.3.4) on top of
3
+ * `Intl.Collator`, which the `fallback=yes` default allows: `lang`,
4
+ * `strength`, `numeric`, `caseFirst` and `alternate` are honoured, the
5
+ * quaternary and identical strengths through a tie-break.
6
+ *
7
+ * @module @tradik/xslt3/functions/ucaCollation
8
+ */
9
+
10
+ import { XPathError } from "../errors.js";
11
+ import { compareCodepoints } from "../xdm/strings.js";
12
+
13
+ /** @typedef {import("./collations.js").Collation} Collation */
14
+
15
+ const strengths = {
16
+ primary: "base",
17
+ 1: "base",
18
+ secondary: "accent",
19
+ 2: "accent",
20
+ tertiary: "variant",
21
+ 3: "variant",
22
+ };
23
+
24
+ /**
25
+ * UCA parameters that Intl.Collator honours, with their valid values
26
+ * (null: any value).
27
+ */
28
+ const SUPPORTED = new Map([
29
+ ["fallback", ["yes", "no"]],
30
+ ["lang", null],
31
+ [
32
+ "strength",
33
+ Object.keys(strengths).concat(["quaternary", "identical", "4", "5"]),
34
+ ],
35
+ ["numeric", ["yes", "no"]],
36
+ ["caseFirst", ["upper", "lower"]],
37
+ ["alternate", ["non-ignorable", "shifted", "blanked"]],
38
+ ]);
39
+
40
+ /**
41
+ * Whether a UCA parameter is honoured with this value.
42
+ * @param {string} key
43
+ * @param {string} value
44
+ * @returns {boolean}
45
+ */
46
+ function isSupported(key, value) {
47
+ if (!SUPPORTED.has(key)) return false;
48
+ const values = SUPPORTED.get(key);
49
+ return values === null || values.includes(value);
50
+ }
51
+
52
+ /**
53
+ * The comparison of strings that the collator finds equal, for the levels
54
+ * Intl.Collator lacks: at strength identical any difference counts (the
55
+ * NFD forms compared by codepoint, UTS #10 section 4.3); at strength
56
+ * quaternary with alternate=shifted, the ignored variable characters
57
+ * count (with alternate=blanked they stay ignored).
58
+ * @param {string|undefined} strength
59
+ * @param {string|undefined} alternate
60
+ * @param {Intl.Collator} collator
61
+ * @returns {(a: string, b: string) => number}
62
+ */
63
+ function tieBreaker(strength, alternate, collator) {
64
+ if (strength === "identical" || strength === "5") {
65
+ return (a, b) => compareCodepoints(a.normalize("NFD"), b.normalize("NFD"));
66
+ }
67
+ if (
68
+ (strength === "quaternary" || strength === "4") &&
69
+ alternate === "shifted"
70
+ ) {
71
+ const { locale, ...options } = collator.resolvedOptions();
72
+ const full = new Intl.Collator(locale, {
73
+ ...options,
74
+ ignorePunctuation: false,
75
+ });
76
+ return (a, b) => Math.sign(full.compare(a, b));
77
+ }
78
+ return () => 0;
79
+ }
80
+
81
+ /**
82
+ * Builds a collation from a UCA collation URI with `Intl.Collator`. Only
83
+ * `lang`, `strength`, `numeric`, `caseFirst` and `alternate` (as
84
+ * ignorePunctuation) are honoured; the others fall back to the defaults.
85
+ * @param {string} uri
86
+ * @returns {Collation}
87
+ * @throws {XPathError} FOCH0002 when `fallback=no` asks for an unsupported
88
+ * parameter or value
89
+ */
90
+ export function ucaCollation(uri) {
91
+ const query = uri.includes("?") ? uri.slice(uri.indexOf("?") + 1) : "";
92
+ const params = new Map(
93
+ query
94
+ .split(";")
95
+ .filter(Boolean)
96
+ .map((pair) => pair.split("=")),
97
+ );
98
+ if (
99
+ params.get("fallback") === "no" &&
100
+ [...params].some(([key, value]) => !isSupported(key, value))
101
+ ) {
102
+ throw new XPathError("FOCH0002", `Unsupported collation ${uri}`);
103
+ }
104
+ const caseFirst = params.get("caseFirst");
105
+ const options = {
106
+ sensitivity: strengths[params.get("strength")] ?? "variant",
107
+ numeric: params.get("numeric") === "yes",
108
+ caseFirst:
109
+ caseFirst === "upper" || caseFirst === "lower" ? caseFirst : "false",
110
+ ignorePunctuation: ["blanked", "shifted"].includes(params.get("alternate")),
111
+ };
112
+ let collator;
113
+ try {
114
+ collator = new Intl.Collator(params.get("lang") || "en", options);
115
+ } catch {
116
+ collator = new Intl.Collator("en", options);
117
+ }
118
+ const strength = params.get("strength");
119
+ const tieBreak = tieBreaker(strength, params.get("alternate"), collator);
120
+ return Object.freeze({
121
+ uri,
122
+ compare: (a, b) => Math.sign(collator.compare(a, b)) || tieBreak(a, b),
123
+ key: null,
124
+ // numeric collation units have no substrings ("10" is one unit)
125
+ substrings: !options.numeric,
126
+ });
127
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * fn:unparsed-text, fn:unparsed-text-lines and fn:unparsed-text-available
3
+ * (F&O 3.1 section 14.6), through the dynamic context hook `loadText`
4
+ * (see xpath/eval/resources.js for the loader and its default).
5
+ *
6
+ * @module @tradik/xslt3/functions/unparsedText
7
+ */
8
+
9
+ import { booleanItem, define, stringItem } from "./support.js";
10
+
11
+ /**
12
+ * Lines of a text: split at CRLF, CR or LF; a final line ending does not
13
+ * start an empty line.
14
+ * @param {string} text
15
+ * @returns {string[]}
16
+ */
17
+ export function splitLines(text) {
18
+ if (text === "") return [];
19
+ const lines = text.split(/\r\n|\r|\n/);
20
+ if (lines[lines.length - 1] === "") lines.pop();
21
+ return lines;
22
+ }
23
+
24
+ /**
25
+ * Declares a function of the family in its two arities.
26
+ * @param {string} local
27
+ * @param {string} returns
28
+ * @param {(load: () => string) => Array} impl - Gets the loading of the
29
+ * resource, not called for an empty href
30
+ * @param {Array} empty - Result for an empty href
31
+ * @returns {object[]}
32
+ */
33
+ function family(local, returns, impl, empty) {
34
+ const run = ([href, encoding], context) =>
35
+ href.length
36
+ ? impl(() => context.loadText(href[0].value, encoding?.[0].value))
37
+ : empty;
38
+ return [
39
+ define(local, ["xs:string?"], returns, run),
40
+ define(local, ["xs:string?", "xs:string"], returns, run),
41
+ ];
42
+ }
43
+
44
+ /**
45
+ * fn:unparsed-text-available: whether loading succeeds (every error of
46
+ * the loading is a FOUT error).
47
+ * @param {() => string} load
48
+ * @returns {Array}
49
+ */
50
+ function available(load) {
51
+ try {
52
+ load();
53
+ return [booleanItem(true)];
54
+ } catch {
55
+ return [booleanItem(false)];
56
+ }
57
+ }
58
+
59
+ /** Function definitions. */
60
+ export const unparsedTextFunctions = [
61
+ ...family("unparsed-text", "xs:string?", (load) => [stringItem(load())], []),
62
+ ...family(
63
+ "unparsed-text-lines",
64
+ "xs:string*",
65
+ (load) => splitLines(load()).map(stringItem),
66
+ [],
67
+ ),
68
+ ...family("unparsed-text-available", "xs:boolean", available, [
69
+ booleanItem(false),
70
+ ]),
71
+ ];