@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,199 @@
1
+ /**
2
+ * Functions on nodes (F&O 3.1 section 13): names, root, children,
3
+ * innermost/outermost, generate-id and lang. Functions with an optional
4
+ * node argument use the context item when it is omitted.
5
+ *
6
+ * @module @tradik/xslt3/functions/nodes
7
+ */
8
+
9
+ import { AtomicValue } from "../xdm/atomic.js";
10
+ import { QNameValue } from "../xdm/qname.js";
11
+ import { types } from "../xdm/types.js";
12
+ import { booleanItem, stringItem } from "../xpath/eval/atomics.js";
13
+ import {
14
+ childrenOf,
15
+ nodeLocalName,
16
+ nodeNamespace,
17
+ nodePrefix,
18
+ parentOf,
19
+ rootOf,
20
+ } from "../xpath/eval/domNodes.js";
21
+ import { focusNode } from "./focus.js";
22
+ import { nodePathFunctions } from "./nodePaths.js";
23
+
24
+ /**
25
+ * Definitions of a function of an optional node, with the context node as
26
+ * default (arity 0 and 1).
27
+ * @param {string} local
28
+ * @param {string} returns
29
+ * @param {(node: Node|undefined, context: object) => Array} impl - Called
30
+ * with undefined for an empty argument
31
+ * @returns {object[]}
32
+ */
33
+ export function nodeFunction(local, returns, impl) {
34
+ return [
35
+ {
36
+ local,
37
+ params: [],
38
+ returns,
39
+ focus: true,
40
+ impl: (_, context) => impl(focusNode(context), context),
41
+ },
42
+ {
43
+ local,
44
+ params: ["node()?"],
45
+ returns,
46
+ impl: ([arg], context) => impl(arg[0], context),
47
+ },
48
+ ];
49
+ }
50
+
51
+ /** @param {Node} node @returns {boolean} whether the node kind has a name */
52
+ const isNamed = (node) =>
53
+ [1, 2, 7].includes(node.nodeType) ||
54
+ (node.nodeType === 13 && node.localName !== "");
55
+
56
+ /** @param {Node} node @returns {string} the lexical QName of a node */
57
+ function lexicalName(node) {
58
+ if (!isNamed(node)) return "";
59
+ const prefix = nodePrefix(node);
60
+ const local = nodeLocalName(node);
61
+ return prefix ? `${prefix}:${local}` : local;
62
+ }
63
+
64
+ /** @type {WeakMap<object, string>} */
65
+ const ids = new WeakMap();
66
+ let nextId = 0;
67
+
68
+ /**
69
+ * @param {Node} node
70
+ * @returns {string} an identifier unique to the node
71
+ */
72
+ function generateId(node) {
73
+ let id = ids.get(node);
74
+ if (id === undefined) {
75
+ id = `n${nextId++}`;
76
+ ids.set(node, id);
77
+ }
78
+ return id;
79
+ }
80
+
81
+ /**
82
+ * fn:lang: whether the xml:lang of a node matches a language.
83
+ * @param {string} language
84
+ * @param {Node} node
85
+ * @returns {boolean}
86
+ */
87
+ function matchesLanguage(language, node) {
88
+ const xml = "http://www.w3.org/XML/1998/namespace";
89
+ for (let n = node; n; n = parentOf(n)) {
90
+ const value = n.nodeType === 1 ? n.getAttributeNS(xml, "lang") : null;
91
+ if (value !== null && n.hasAttributeNS(xml, "lang")) {
92
+ const actual = value.toLowerCase();
93
+ const wanted = language.toLowerCase();
94
+ return actual === wanted || actual.startsWith(`${wanted}-`);
95
+ }
96
+ }
97
+ return false;
98
+ }
99
+
100
+ /**
101
+ * The topmost or bottommost nodes of a set.
102
+ * @param {Node[]} nodes
103
+ * @param {object} context
104
+ * @param {boolean} inner - innermost (else outermost)
105
+ * @returns {Node[]}
106
+ */
107
+ function nesting(nodes, context, inner) {
108
+ const set = new Set(nodes);
109
+ const hasAncestorIn = (node) => {
110
+ for (let p = parentOf(node); p; p = parentOf(p)) {
111
+ if (set.has(p)) return true;
112
+ }
113
+ return false;
114
+ };
115
+ const ancestors = new Set();
116
+ if (inner) {
117
+ for (const node of nodes) {
118
+ for (let p = parentOf(node); p; p = parentOf(p)) ancestors.add(p);
119
+ }
120
+ }
121
+ const kept = nodes.filter((node) =>
122
+ inner ? !ancestors.has(node) : !hasAncestorIn(node),
123
+ );
124
+ return context.order.sort(kept);
125
+ }
126
+
127
+ /** Function definitions. */
128
+ export const nodeFunctions = [
129
+ ...nodeFunction("name", "xs:string", (node) => [
130
+ stringItem(node ? lexicalName(node) : ""),
131
+ ]),
132
+ ...nodeFunction("local-name", "xs:string", (node) => [
133
+ stringItem(node && isNamed(node) ? nodeLocalName(node) : ""),
134
+ ]),
135
+ ...nodeFunction("namespace-uri", "xs:anyURI", (node) => [
136
+ new AtomicValue(
137
+ types.anyURI,
138
+ node && (node.nodeType === 1 || node.nodeType === 2)
139
+ ? nodeNamespace(node)
140
+ : "",
141
+ ),
142
+ ]),
143
+ ...nodeFunction("node-name", "xs:QName?", (node) =>
144
+ node && isNamed(node)
145
+ ? [
146
+ new AtomicValue(
147
+ types.QName,
148
+ new QNameValue(
149
+ nodeNamespace(node),
150
+ nodeLocalName(node),
151
+ nodePrefix(node),
152
+ ),
153
+ ),
154
+ ]
155
+ : [],
156
+ ),
157
+ ...nodeFunction("root", "node()?", (node) => (node ? [rootOf(node)] : [])),
158
+ ...nodeFunction("has-children", "xs:boolean", (node) => [
159
+ booleanItem(Boolean(node) && childrenOf(node).length > 0),
160
+ ]),
161
+ ...nodeFunction("nilled", "xs:boolean?", (node) =>
162
+ node?.nodeType === 1 ? [booleanItem(false)] : [],
163
+ ),
164
+ ...nodeFunction("generate-id", "xs:string", (node) => [
165
+ stringItem(node ? generateId(node) : ""),
166
+ ]),
167
+ {
168
+ local: "innermost",
169
+ params: ["node()*"],
170
+ returns: "node()*",
171
+ impl: ([nodes], context) => nesting(nodes, context, true),
172
+ },
173
+ {
174
+ local: "outermost",
175
+ params: ["node()*"],
176
+ returns: "node()*",
177
+ impl: ([nodes], context) => nesting(nodes, context, false),
178
+ },
179
+ {
180
+ local: "lang",
181
+ params: ["xs:string?"],
182
+ returns: "xs:boolean",
183
+ focus: true,
184
+ impl: ([language], context) => [
185
+ booleanItem(
186
+ matchesLanguage(language[0]?.value ?? "", focusNode(context)),
187
+ ),
188
+ ],
189
+ },
190
+ {
191
+ local: "lang",
192
+ params: ["xs:string?", "node()"],
193
+ returns: "xs:boolean",
194
+ impl: ([language, [node]]) => [
195
+ booleanItem(matchesLanguage(language[0]?.value ?? "", node)),
196
+ ],
197
+ },
198
+ ...nodePathFunctions,
199
+ ];
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Functions on numeric values (F&O 3.1 section 4.4): abs, ceiling, floor,
3
+ * round and round-half-to-even. Results have the primitive numeric type of
4
+ * the argument (xs:integer for integer subtypes). Decimal and integer
5
+ * rounding is exact; doubles (and floats, widened to doubles) are rounded
6
+ * through the shortest decimal that reads back as the double, so
7
+ * round(0.49999999999999994) is 0.
8
+ *
9
+ * @module @tradik/xslt3/functions/numeric
10
+ */
11
+
12
+ import { AtomicValue } from "../xdm/atomic.js";
13
+ import { Decimal } from "../xdm/decimal.js";
14
+ import { numberToDecimal } from "../xdm/numeric.js";
15
+ import { types } from "../xdm/types.js";
16
+ import { define } from "./support.js";
17
+
18
+ /**
19
+ * Rounds a decimal to `precision` fraction digits (negative: to a power of
20
+ * ten).
21
+ * @param {Decimal} value
22
+ * @param {number} precision
23
+ * @param {boolean} halfEven - Ties to even; otherwise toward +INF
24
+ * @returns {Decimal}
25
+ */
26
+ export function roundDecimal(value, precision, halfEven) {
27
+ if (value.scale <= precision) return value;
28
+ const divisor = 10n ** BigInt(value.scale - precision);
29
+ let quotient = value.unscaled / divisor;
30
+ let remainder = value.unscaled % divisor;
31
+ if (remainder < 0n) {
32
+ quotient -= 1n;
33
+ remainder += divisor;
34
+ }
35
+ const twice = 2n * remainder;
36
+ const up =
37
+ twice > divisor ||
38
+ (twice === divisor && (!halfEven || quotient % 2n !== 0n));
39
+ return Decimal.of(up ? quotient + 1n : quotient, precision);
40
+ }
41
+
42
+ /**
43
+ * @param {AtomicValue} item
44
+ * @param {*} value
45
+ * @returns {AtomicValue} the value with the item's primitive numeric type
46
+ */
47
+ function sameType(item, value) {
48
+ const type = typeof value === "bigint" ? types.integer : item.type.primitive;
49
+ return new AtomicValue(type, value);
50
+ }
51
+
52
+ /**
53
+ * Rounds a numeric item.
54
+ * @param {AtomicValue} item
55
+ * @param {number} precision - Already clamped to a safe range
56
+ * @param {boolean} halfEven
57
+ * @returns {AtomicValue}
58
+ */
59
+ function roundItem(item, precision, halfEven) {
60
+ const { value } = item;
61
+ if (typeof value === "bigint") {
62
+ if (precision >= 0) return sameType(item, value);
63
+ return sameType(
64
+ item,
65
+ roundDecimal(Decimal.of(value), precision, halfEven).unscaled,
66
+ );
67
+ }
68
+ if (value instanceof Decimal) {
69
+ return sameType(item, roundDecimal(value, precision, halfEven));
70
+ }
71
+ if (!Number.isFinite(value) || value === 0) return sameType(item, value);
72
+ const isFloat = item.type.primitive === types.float;
73
+ // floats through their double value: xs:float("0.05") is above 0.05
74
+ const exact = numberToDecimal(value);
75
+ let rounded = Number(roundDecimal(exact, precision, halfEven).toString());
76
+ if (rounded === 0 && value < 0) rounded = -0;
77
+ return sameType(item, isFloat ? Math.fround(rounded) : rounded);
78
+ }
79
+
80
+ /** Largest precision that can change a double or decimal in practice. */
81
+ const PRECISION_LIMIT = 10000;
82
+
83
+ /**
84
+ * @param {Array<*>[]} args - [$arg, $precision?]
85
+ * @param {boolean} halfEven
86
+ * @returns {Array<*>}
87
+ */
88
+ function roundFunction([arg, precision], halfEven) {
89
+ if (arg.length === 0) return [];
90
+ const p = precision === undefined ? 0n : precision[0].value;
91
+ const clamped =
92
+ p > BigInt(PRECISION_LIMIT)
93
+ ? PRECISION_LIMIT
94
+ : p < BigInt(-PRECISION_LIMIT)
95
+ ? -PRECISION_LIMIT
96
+ : Number(p);
97
+ return [roundItem(arg[0], clamped, halfEven)];
98
+ }
99
+
100
+ /**
101
+ * Declares a one-argument numeric function.
102
+ * @param {string} local
103
+ * @param {(value: *, item: AtomicValue) => *} apply - On the value
104
+ * @returns {import("./support.js").FunctionDefinition}
105
+ */
106
+ const unary = (local, apply) =>
107
+ define(local, ["xs:numeric?"], "xs:numeric?", ([arg]) =>
108
+ arg.length === 0 ? [] : [sameType(arg[0], apply(arg[0].value))],
109
+ );
110
+
111
+ /** @param {*} v @returns {*} the largest integer not above v */
112
+ const floor = (v) =>
113
+ typeof v === "bigint"
114
+ ? v
115
+ : v instanceof Decimal
116
+ ? Decimal.of(v.floor())
117
+ : Math.floor(v);
118
+
119
+ const N = "xs:numeric?";
120
+
121
+ /** @type {import("./support.js").FunctionDefinition[]} */
122
+ export const numericFunctions = [
123
+ unary("abs", (v) =>
124
+ typeof v === "bigint"
125
+ ? v < 0n
126
+ ? -v
127
+ : v
128
+ : v instanceof Decimal
129
+ ? v.sign() < 0
130
+ ? v.neg()
131
+ : v
132
+ : Math.abs(v),
133
+ ),
134
+ unary("floor", floor),
135
+ unary("ceiling", (v) =>
136
+ typeof v === "bigint"
137
+ ? v
138
+ : v instanceof Decimal
139
+ ? floor(v.neg()).neg()
140
+ : Math.ceil(v),
141
+ ),
142
+ define("round", [N], N, (args) => roundFunction(args, false)),
143
+ define("round", [N, "xs:integer"], N, (args) => roundFunction(args, false)),
144
+ define("round-half-to-even", [N], N, (args) => roundFunction(args, true)),
145
+ define("round-half-to-even", [N, "xs:integer"], N, (args) =>
146
+ roundFunction(args, true),
147
+ ),
148
+ ];
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Option maps (F&O 3.1 section 1.5, "Options"): the `$options` argument
3
+ * of fn:parse-json, fn:json-to-xml, fn:xml-to-json... Each known option
4
+ * is converted to its declared type with the function conversion rules
5
+ * (XPTY0004 when it cannot be), checked against its allowed values, and
6
+ * defaulted when absent; unknown options are ignored.
7
+ *
8
+ * @module @tradik/xslt3/functions/options
9
+ */
10
+
11
+ import { XPathError } from "../errors.js";
12
+ import { stringItem } from "../xpath/eval/atomics.js";
13
+ import { coerce } from "../xpath/eval/coercion.js";
14
+ import { sequenceTypeOf } from "../xpath/eval/functionItems.js";
15
+
16
+ /**
17
+ * Declaration of one option.
18
+ * @typedef {object} OptionSpec
19
+ * @property {string} type - Sequence type of the value, e.g. "xs:boolean"
20
+ * @property {*} [default] - Value when the option is absent
21
+ * @property {string[]} [values] - Allowed values of a string option
22
+ */
23
+
24
+ /**
25
+ * Reads an option map.
26
+ * @param {import("../items/map.js").XdmMap|undefined} map - The options
27
+ * argument, undefined for the arity without it
28
+ * @param {Record<string, OptionSpec>} specs - Known options by key
29
+ * @param {string} invalidCode - Error code of a value outside `values`
30
+ * (FOJS0005 for the JSON functions)
31
+ * @returns {Record<string, *>} option values by key: the JS value of
32
+ * single atomic values (boolean, string), the item itself for functions,
33
+ * `undefined` for absent options without default
34
+ * @throws {XPathError} XPTY0004 for a value of the wrong type,
35
+ * `invalidCode` for a value that is not allowed
36
+ */
37
+ export function readOptions(map, specs, invalidCode) {
38
+ const result = {};
39
+ for (const [key, spec] of Object.entries(specs)) {
40
+ const value = map?.get(stringItem(key));
41
+ if (value === undefined) {
42
+ result[key] = spec.default;
43
+ continue;
44
+ }
45
+ const [item] = coerce(value, sequenceTypeOf(spec.type), {
46
+ what: `option "${key}"`,
47
+ });
48
+ const plain = item?.value === undefined ? item : item.value;
49
+ if (spec.values && !spec.values.includes(plain)) {
50
+ throw new XPathError(
51
+ invalidCode,
52
+ `Invalid value "${plain}" of option "${key}"`,
53
+ );
54
+ }
55
+ result[key] = plain;
56
+ }
57
+ return result;
58
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * fn:parse-ietf-date (F&O 3.1 section 9.9.1): the date formats of HTTP
3
+ * (RFC 822/1123, RFC 850 and asctime) to xs:dateTime, case-insensitive,
4
+ * with the liberal grammar of the specification.
5
+ *
6
+ * @module @tradik/xslt3/functions/parseIetfDate
7
+ */
8
+
9
+ import { XPathError } from "../errors.js";
10
+ import { fromLexical } from "../xdm/lexical.js";
11
+ import { define } from "./support.js";
12
+
13
+ const MONTHS = "jan feb mar apr may jun jul aug sep oct nov dec".split(" ");
14
+ const ZONES = {
15
+ ut: 0,
16
+ utc: 0,
17
+ gmt: 0,
18
+ est: -5,
19
+ edt: -4,
20
+ cst: -6,
21
+ cdt: -5,
22
+ mst: -7,
23
+ mdt: -6,
24
+ pst: -8,
25
+ pdt: -7,
26
+ };
27
+
28
+ const S = "[\\t\\n\\r ]+";
29
+ const DAY_NAME =
30
+ "(?:monday|tuesday|wednesday|thursday|friday|saturday|sunday|mon|tue|wed|thu|fri|sat|sun)";
31
+ const MONTH = `(${MONTHS.join("|")})`;
32
+ const DSEP = `(?:${S}|(?:${S})?-(?:${S})?)`;
33
+ const TZ_NAME = "(?:utc|ut|gmt|est|edt|cst|cdt|mst|mdt|pst|pdt)";
34
+ const TZ_OFFSET = `([+-])(\\d\\d?:(?:\\d\\d)?|\\d{1,4})(?:(?:${S})?\\((?:${S})?${TZ_NAME}(?:${S})?\\))?`;
35
+ const TIME = `(\\d\\d?):(\\d\\d)(?::(\\d\\d(?:\\.\\d+)?))?(?:(?:${S})?(?:(${TZ_NAME})|${TZ_OFFSET}))?`;
36
+ const YEAR = "(\\d\\d(?:\\d\\d)?)";
37
+ const DATE_SPEC = `(\\d\\d?)${DSEP}${MONTH}${DSEP}${YEAR}${S}${TIME}`;
38
+ const ASC_TIME = `${MONTH}${DSEP}(\\d\\d?)${S}${TIME}${S}${YEAR}`;
39
+ const INPUT = new RegExp(
40
+ `^(?:${S})?(?:${DAY_NAME},?${S})?(?:${DATE_SPEC}|${ASC_TIME})(?:${S})?$`,
41
+ "i",
42
+ );
43
+
44
+ /** @param {string|number} n @param {number} width @returns {string} */
45
+ const pad = (n, width) => String(n).padStart(width, "0");
46
+
47
+ /**
48
+ * The timezone of a parsed date: offset, else zone name, else UTC.
49
+ * @param {string|undefined} name
50
+ * @param {string|undefined} sign
51
+ * @param {string|undefined} offset - "H", "HH", "HMM", "HHMM" or "H:MM"
52
+ * @returns {string} "Z" or "+HH:MM"
53
+ */
54
+ function timezone(name, sign, offset) {
55
+ if (offset !== undefined) {
56
+ const [hours, minutes = "00"] = offset.includes(":")
57
+ ? offset.split(":")
58
+ : offset.length > 2
59
+ ? [offset.slice(0, -2), offset.slice(-2)]
60
+ : [offset];
61
+ return `${sign}${pad(hours, 2)}:${pad(minutes || "00", 2)}`;
62
+ }
63
+ const hours = ZONES[name?.toLowerCase()] ?? 0;
64
+ return hours === 0 ? "Z" : `-${pad(-hours, 2)}:00`;
65
+ }
66
+
67
+ /**
68
+ * Parses an IETF date.
69
+ * @param {string} text
70
+ * @returns {import("../xdm/atomic.js").AtomicValue} an xs:dateTime
71
+ * @throws {XPathError} FORG0010 when the text does not match the grammar
72
+ * or is not a valid date and time
73
+ */
74
+ export function parseIetfDate(text) {
75
+ const match = INPUT.exec(text);
76
+ if (!match) throw new XPathError("FORG0010", `Invalid IETF date "${text}"`);
77
+ // groups: datespec 1-9 (day, month, year, time 6), asctime 10-18
78
+ // (month, day, time 6, year)
79
+ const spec = match[1] !== undefined;
80
+ const g = spec ? match.slice(1, 10) : match.slice(10, 19);
81
+ const [day, month, year] = spec ? g : [g[1], g[0], g[8]];
82
+ const [hours, minutes, seconds = "00", name, sign, offset] = spec
83
+ ? g.slice(3, 9)
84
+ : g.slice(2, 8);
85
+ const fullYear = year.length === 2 ? `19${year}` : year;
86
+ const lexical =
87
+ `${fullYear}-${pad(MONTHS.indexOf(month.toLowerCase()) + 1, 2)}-` +
88
+ `${pad(day, 2)}T${pad(hours, 2)}:${minutes}:${pad(seconds, 2)}` +
89
+ timezone(name, sign, offset);
90
+ try {
91
+ return fromLexical("xs:dateTime", lexical);
92
+ } catch (error) {
93
+ throw new XPathError("FORG0010", `Invalid IETF date "${text}"`, {
94
+ cause: error,
95
+ });
96
+ }
97
+ }
98
+
99
+ /** Function definitions. */
100
+ export const parseIetfDateFunctions = [
101
+ define("parse-ietf-date", ["xs:string?"], "xs:dateTime?", ([value]) =>
102
+ value.length ? [parseIetfDate(value[0].value)] : [],
103
+ ),
104
+ ];
@@ -0,0 +1,77 @@
1
+ /**
2
+ * fn:parse-xml and fn:parse-xml-fragment (F&O 3.1 sections 14.6.2 and
3
+ * 14.6.3), through the dynamic context hook `parseXml` (see
4
+ * xpath/eval/resources.js). The documents have the static base URI as
5
+ * base URI and no document URI; a fragment is a document fragment (a
6
+ * document node that may hold several elements and text).
7
+ *
8
+ * @module @tradik/xslt3/functions/parseXml
9
+ */
10
+
11
+ import { XPathError } from "../errors.js";
12
+ import { setBaseUri } from "./baseUris.js";
13
+ import { define } from "./support.js";
14
+
15
+ /** A text declaration: optional version, required encoding. */
16
+ const TEXT_DECLARATION =
17
+ /^<\?xml(?:\s+version\s*=\s*(["'])1\.\d+\1)?\s+encoding\s*=\s*(["'])[A-Za-z][\w.-]*\2\s*\?>/;
18
+
19
+ /**
20
+ * Parses with the hook, reporting failures as FODC0006.
21
+ * @param {string} text
22
+ * @param {object} context
23
+ * @returns {Document}
24
+ */
25
+ function parse(text, context) {
26
+ try {
27
+ return context.parseXml(text, context.staticBaseUri);
28
+ } catch (error) {
29
+ if (error.code === "FODC0006") throw error;
30
+ throw new XPathError("FODC0006", "The string is not well-formed XML", {
31
+ cause: error,
32
+ });
33
+ }
34
+ }
35
+
36
+ /**
37
+ * fn:parse-xml-fragment of a string: the content of an external parsed
38
+ * entity, parsed inside a wrapper element.
39
+ * @param {string} text
40
+ * @param {object} context
41
+ * @returns {DocumentFragment}
42
+ */
43
+ function parseFragment(text, context) {
44
+ let content = text;
45
+ if (/^<\?xml[\s?]/.test(text)) {
46
+ const declaration = TEXT_DECLARATION.exec(text);
47
+ if (!declaration) {
48
+ throw new XPathError("FODC0006", "Invalid text declaration");
49
+ }
50
+ content = text.slice(declaration[0].length);
51
+ }
52
+ const document = parse(`<fragment>${content}</fragment>`, context);
53
+ const fragment = document.createDocumentFragment();
54
+ const wrapper = document.documentElement;
55
+ while (wrapper.firstChild) fragment.appendChild(wrapper.firstChild);
56
+ return setBaseUri(fragment, context.staticBaseUri);
57
+ }
58
+
59
+ /** Function definitions. */
60
+ export const parseXmlFunctions = [
61
+ define(
62
+ "parse-xml",
63
+ ["xs:string?"],
64
+ "document-node(element(*))?",
65
+ ([arg], context) =>
66
+ arg.length
67
+ ? [setBaseUri(parse(arg[0].value, context), context.staticBaseUri)]
68
+ : [],
69
+ ),
70
+ define(
71
+ "parse-xml-fragment",
72
+ ["xs:string?"],
73
+ "document-node()?",
74
+ ([arg], context) =>
75
+ arg.length ? [parseFragment(arg[0].value, context)] : [],
76
+ ),
77
+ ];
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Functions on QNames that need no node (F&O 3.1 section 10.2): QName,
3
+ * prefix-from-QName, local-name-from-QName and namespace-uri-from-QName.
4
+ * fn:resolve-QName and fn:namespace-uri-for-prefix need in-scope
5
+ * namespaces of an element and belong to the evaluator.
6
+ *
7
+ * @module @tradik/xslt3/functions/qnames
8
+ */
9
+
10
+ import { XPathError } from "../errors.js";
11
+ import { AtomicValue } from "../xdm/atomic.js";
12
+ import { QNameValue } from "../xdm/qname.js";
13
+ import { namePatterns, types } from "../xdm/types.js";
14
+ import { define } from "./support.js";
15
+
16
+ /**
17
+ * fn:QName on strings.
18
+ * @param {string} uri - "" for no namespace
19
+ * @param {string} lexical - "prefix:local" or "local"
20
+ * @returns {QNameValue}
21
+ * @throws {XPathError} FOCA0002 for an invalid lexical QName or a prefix
22
+ * without namespace
23
+ */
24
+ export function makeQName(uri, lexical) {
25
+ const parts = lexical.split(":");
26
+ if (
27
+ parts.length > 2 ||
28
+ !parts.every((part) => namePatterns.ncName.test(part)) ||
29
+ (parts.length === 2 && uri === "")
30
+ ) {
31
+ throw new XPathError("FOCA0002", `Invalid QName "${lexical}"`);
32
+ }
33
+ const [prefix, local] = parts.length === 2 ? parts : ["", parts[0]];
34
+ return new QNameValue(uri, local, prefix);
35
+ }
36
+
37
+ /**
38
+ * Declares an accessor of an optional xs:QName.
39
+ * @param {string} local
40
+ * @param {string} returns
41
+ * @param {(q: QNameValue) => AtomicValue|null} access
42
+ * @returns {import("./support.js").FunctionDefinition}
43
+ */
44
+ const accessor = (local, returns, access) =>
45
+ define(local, ["xs:QName?"], returns, ([q]) => {
46
+ const result = q.length === 0 ? null : access(q[0].value);
47
+ return result === null ? [] : [result];
48
+ });
49
+
50
+ /** @type {import("./support.js").FunctionDefinition[]} */
51
+ export const qnameFunctions = [
52
+ define("QName", ["xs:string?", "xs:string"], "xs:QName", ([uri, [q]]) => [
53
+ new AtomicValue(types.QName, makeQName(uri[0]?.value ?? "", q.value)),
54
+ ]),
55
+ accessor("prefix-from-QName", "xs:NCName?", (q) =>
56
+ q.prefix === "" ? null : new AtomicValue(types.NCName, q.prefix),
57
+ ),
58
+ accessor(
59
+ "local-name-from-QName",
60
+ "xs:NCName?",
61
+ (q) => new AtomicValue(types.NCName, q.localName),
62
+ ),
63
+ accessor(
64
+ "namespace-uri-from-QName",
65
+ "xs:anyURI?",
66
+ (q) => new AtomicValue(types.anyURI, q.namespaceURI),
67
+ ),
68
+ ];