@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,142 @@
1
+ /**
2
+ * Items of the data model: atomic values, nodes and the function items
3
+ * (maps, arrays, functions) that the evaluator adds later.
4
+ *
5
+ * Atomic value representations by primitive type:
6
+ * - xs:string family, xs:untypedAtomic, xs:anyURI: JS string
7
+ * - xs:boolean: JS boolean
8
+ * - xs:decimal: {@link Decimal}; xs:integer and subtypes: BigInt
9
+ * - xs:double, xs:float: JS number (float values pass through Math.fround)
10
+ * - durations: {@link DurationValue}; dates and times: {@link DateTimeValue}
11
+ * - xs:hexBinary, xs:base64Binary: Uint8Array
12
+ * - xs:QName, xs:NOTATION: {@link QNameValue}
13
+ *
14
+ * A sequence is a plain JS array of items.
15
+ *
16
+ * @module @tradik/xslt3/xdm/atomic
17
+ */
18
+
19
+ import { XPathError } from "../errors.js";
20
+ import { Decimal } from "./decimal.js";
21
+ import { normalizeWhitespace } from "./strings.js";
22
+ import { getType } from "./types.js";
23
+
24
+ /**
25
+ * Property under which maps, arrays and function items declare their kind
26
+ * ("map", "array" or "function"). Arrays also expose `members`, an array
27
+ * of sequences, which atomization flattens.
28
+ */
29
+ export const ITEM_KIND = Symbol.for("@tradik/xslt3/itemKind");
30
+
31
+ /** An immutable atomic value: a type descriptor and a typed value. */
32
+ export class AtomicValue {
33
+ /**
34
+ * Prefer {@link atomic}, which coerces and validates the value.
35
+ * @param {object} type - Type descriptor from the registry
36
+ * @param {*} value - Representation for the type's primitive
37
+ */
38
+ constructor(type, value) {
39
+ this.type = type;
40
+ this.value = value;
41
+ Object.freeze(this);
42
+ }
43
+ }
44
+
45
+ /**
46
+ * @param {*} item
47
+ * @returns {"atomic"|"node"|"map"|"array"|"function"} the kind of an item
48
+ * @throws {XPathError} XPTY0004 for a value that is not an item
49
+ */
50
+ export function itemKind(item) {
51
+ if (item instanceof AtomicValue) return "atomic";
52
+ // Nodes before the symbol: on a DOM node (a jsdom wrapper) the symbol is
53
+ // looked up along a long prototype chain
54
+ if (typeof item?.nodeType === "number") return "node";
55
+ if (item?.[ITEM_KIND]) return item[ITEM_KIND];
56
+ throw new XPathError("XPTY0004", `Not an XDM item: ${String(item)}`);
57
+ }
58
+
59
+ /** @param {*} item @returns {boolean} whether the item is an atomic value */
60
+ export const isAtomic = (item) => item instanceof AtomicValue;
61
+ /** @param {*} item @returns {boolean} whether the item is a DOM node */
62
+ export const isNode = (item) =>
63
+ !isAtomic(item) && typeof item?.nodeType === "number";
64
+ /** @param {*} item @returns {boolean} whether the item is a map */
65
+ export const isMap = (item) => item?.[ITEM_KIND] === "map";
66
+ /** @param {*} item @returns {boolean} whether the item is an array */
67
+ export const isArray = (item) => item?.[ITEM_KIND] === "array";
68
+ /** @param {*} item @returns {boolean} whether the item is a function item (maps and arrays are functions too) */
69
+ export const isFunctionItem = (item) => Boolean(item?.[ITEM_KIND]);
70
+
71
+ /**
72
+ * Checks the constraining facets of a type that is derived by restriction
73
+ * from a primitive (integer ranges, string patterns and whitespace,
74
+ * required timezone).
75
+ * @param {object} type
76
+ * @param {*} value - Representation for the type's primitive
77
+ * @returns {*} the value
78
+ * @throws {XPathError} FORG0001 when a facet is violated
79
+ */
80
+ export function checkFacets(type, value) {
81
+ for (let t = type; t !== t.castPrimitive; t = t.base) {
82
+ const { minInclusive, maxInclusive, pattern, timezoneRequired } = t.facets;
83
+ const fails =
84
+ (minInclusive !== undefined && value < minInclusive) ||
85
+ (maxInclusive !== undefined && value > maxInclusive) ||
86
+ (pattern !== undefined && !pattern.test(value)) ||
87
+ (typeof value === "string" &&
88
+ normalizeWhitespace(value, t.whiteSpace) !== value) ||
89
+ (timezoneRequired === true && value.timezone === null);
90
+ if (fails) {
91
+ throw new XPathError(
92
+ "FORG0001",
93
+ `Value is not a valid ${type.prefixedName}`,
94
+ );
95
+ }
96
+ }
97
+ return value;
98
+ }
99
+
100
+ /** Coercions of JS values to the representation of a primitive. */
101
+ const coercions = {
102
+ integer(value) {
103
+ if (typeof value === "bigint") return value;
104
+ if (Number.isInteger(value)) return BigInt(value);
105
+ throw new XPathError("FORG0001", `${value} is not an integer`);
106
+ },
107
+ decimal(value) {
108
+ if (value instanceof Decimal) return value;
109
+ const decimal = Decimal.parse(
110
+ typeof value === "bigint" ? `${value}` : String(value),
111
+ );
112
+ if (decimal === null) {
113
+ throw new XPathError("FORG0001", `${value} is not a decimal`);
114
+ }
115
+ return decimal;
116
+ },
117
+ float: (value) => Math.fround(value),
118
+ };
119
+
120
+ /**
121
+ * Builds an atomic value from a JS value in the type's representation
122
+ * (numbers and BigInts are accepted for xs:integer and xs:decimal, and
123
+ * floats are rounded); derived types have their facets checked.
124
+ * @param {string|object} typeName - "xs:integer", "integer", Clark name or descriptor
125
+ * @param {*} value
126
+ * @returns {AtomicValue}
127
+ * @throws {XPathError} XPST0080 for abstract types, FORG0001 for invalid
128
+ * values and for xs:error
129
+ * @example atomic("xs:integer", 42); atomic("xs:string", "abc")
130
+ */
131
+ export function atomic(typeName, value) {
132
+ const type = getType(typeName);
133
+ if (type.abstract) {
134
+ throw new XPathError("XPST0080", `${type.prefixedName} is abstract`);
135
+ }
136
+ if (type.localName === "error") {
137
+ throw new XPathError("FORG0001", "xs:error has no values");
138
+ }
139
+ const coerce = coercions[type.castPrimitive.localName];
140
+ const coerced = coerce ? coerce(value) : value;
141
+ return new AtomicValue(type, checkFacets(type, coerced));
142
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * xs:hexBinary and xs:base64Binary: lexical parsing per XSD 1.1 Part 2 and
3
+ * canonical strings. Values are Uint8Arrays; no platform encoder (atob,
4
+ * Buffer) is used, so the module runs anywhere.
5
+ *
6
+ * @module @tradik/xslt3/xdm/binary
7
+ */
8
+
9
+ const alphabet =
10
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
11
+ const base64Pattern =
12
+ /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}[AEIMQUYcgkosw048]=|[A-Za-z0-9+/][AQgw]==)?$/;
13
+
14
+ /**
15
+ * Parses xs:hexBinary (whitespace already collapsed).
16
+ * @param {string} text
17
+ * @returns {Uint8Array|null} null when invalid
18
+ */
19
+ export function parseHexBinary(text) {
20
+ if (text.length % 2 !== 0 || !/^[0-9A-Fa-f]*$/.test(text)) return null;
21
+ const bytes = new Uint8Array(text.length / 2);
22
+ for (let i = 0; i < bytes.length; i++) {
23
+ bytes[i] = parseInt(text.slice(2 * i, 2 * i + 2), 16);
24
+ }
25
+ return bytes;
26
+ }
27
+
28
+ /**
29
+ * @param {Uint8Array} bytes
30
+ * @returns {string} canonical xs:hexBinary (upper case)
31
+ */
32
+ export function formatHexBinary(bytes) {
33
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0"))
34
+ .join("")
35
+ .toUpperCase();
36
+ }
37
+
38
+ /**
39
+ * Parses xs:base64Binary (whitespace already collapsed; single spaces
40
+ * between characters are allowed by the XSD grammar).
41
+ * @param {string} text
42
+ * @returns {Uint8Array|null} null when invalid
43
+ */
44
+ export function parseBase64Binary(text) {
45
+ const compact = text.replaceAll(" ", "");
46
+ if (!base64Pattern.test(compact)) return null;
47
+ const data = compact.replace(/=+$/, "");
48
+ const bytes = new Uint8Array(Math.floor((data.length * 3) / 4));
49
+ let buffer = 0;
50
+ let bits = 0;
51
+ let index = 0;
52
+ for (const char of data) {
53
+ buffer = ((buffer << 6) | alphabet.indexOf(char)) & 0xfff;
54
+ bits += 6;
55
+ if (bits >= 8) {
56
+ bits -= 8;
57
+ bytes[index++] = (buffer >> bits) & 0xff;
58
+ }
59
+ }
60
+ return bytes;
61
+ }
62
+
63
+ /**
64
+ * @param {Uint8Array} bytes
65
+ * @returns {string} canonical xs:base64Binary (no whitespace, padded)
66
+ */
67
+ export function formatBase64Binary(bytes) {
68
+ let out = "";
69
+ for (let i = 0; i < bytes.length; i += 3) {
70
+ const chunk =
71
+ (bytes[i] << 16) | ((bytes[i + 1] ?? 0) << 8) | (bytes[i + 2] ?? 0);
72
+ const chars = Math.min(4, Math.ceil(((bytes.length - i) * 8) / 6));
73
+ for (let j = 0; j < 4; j++) {
74
+ out += j < chars ? alphabet[(chunk >> (18 - 6 * j)) & 63] : "=";
75
+ }
76
+ }
77
+ return out;
78
+ }
79
+
80
+ /**
81
+ * Compares two byte arrays lexicographically (op:hexBinary-less-than).
82
+ * @param {Uint8Array} a
83
+ * @param {Uint8Array} b
84
+ * @returns {number} -1, 0 or 1
85
+ */
86
+ export function compareBytes(a, b) {
87
+ const length = Math.min(a.length, b.length);
88
+ for (let i = 0; i < length; i++) {
89
+ if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1;
90
+ }
91
+ return Math.sign(a.length - b.length);
92
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Proleptic Gregorian calendar arithmetic with astronomical year
3
+ * numbering, as in XSD 1.1 (year 0000 is 1 BCE and is a leap year).
4
+ *
5
+ * @module @tradik/xslt3/xdm/calendar
6
+ */
7
+
8
+ /**
9
+ * @param {number} year
10
+ * @returns {boolean} whether the year is a leap year
11
+ */
12
+ export function isLeapYear(year) {
13
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
14
+ }
15
+
16
+ const monthLengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
17
+
18
+ /**
19
+ * @param {number} year
20
+ * @param {number} month - 1 to 12
21
+ * @returns {number} number of days in the month
22
+ */
23
+ export function daysInMonth(year, month) {
24
+ return month === 2 && isLeapYear(year) ? 29 : monthLengths[month - 1];
25
+ }
26
+
27
+ /**
28
+ * Days since 1970-01-01 of a civil date (H. Hinnant's algorithm).
29
+ * @param {number} year
30
+ * @param {number} month - 1 to 12
31
+ * @param {number} day - 1 to 31
32
+ * @returns {number}
33
+ */
34
+ export function daysFromCivil(year, month, day) {
35
+ const y = month <= 2 ? year - 1 : year;
36
+ const era = Math.floor(y / 400);
37
+ const yearOfEra = y - era * 400;
38
+ const dayOfYear =
39
+ Math.floor((153 * (month + (month > 2 ? -3 : 9)) + 2) / 5) + day - 1;
40
+ const dayOfEra =
41
+ yearOfEra * 365 +
42
+ Math.floor(yearOfEra / 4) -
43
+ Math.floor(yearOfEra / 100) +
44
+ dayOfYear;
45
+ return era * 146097 + dayOfEra - 719468;
46
+ }
47
+
48
+ /**
49
+ * Civil date of a day number (inverse of {@link daysFromCivil}).
50
+ * @param {number} days - Days since 1970-01-01
51
+ * @returns {[number, number, number]} [year, month, day]
52
+ */
53
+ export function civilFromDays(days) {
54
+ const z = days + 719468;
55
+ const era = Math.floor(z / 146097);
56
+ const dayOfEra = z - era * 146097;
57
+ const yearOfEra = Math.floor(
58
+ (dayOfEra -
59
+ Math.floor(dayOfEra / 1460) +
60
+ Math.floor(dayOfEra / 36524) -
61
+ Math.floor(dayOfEra / 146096)) /
62
+ 365,
63
+ );
64
+ const dayOfYear =
65
+ dayOfEra -
66
+ (365 * yearOfEra + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100));
67
+ const mp = Math.floor((5 * dayOfYear + 2) / 153);
68
+ const day = dayOfYear - Math.floor((153 * mp + 2) / 5) + 1;
69
+ const month = mp < 10 ? mp + 3 : mp - 9;
70
+ return [yearOfEra + era * 400 + (month <= 2 ? 1 : 0), month, day];
71
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Casting between atomic types (F&O 3.1 section 19): the primitive-to-
3
+ * primitive table of 19.1, casting from strings (19.2) and casting to and
4
+ * from derived types (19.3).
5
+ *
6
+ * @module @tradik/xslt3/xdm/cast
7
+ */
8
+
9
+ import { XPathError } from "../errors.js";
10
+ import { AtomicValue, checkFacets } from "./atomic.js";
11
+ import { componentsOf, DateTimeValue } from "./datetime.js";
12
+ import { Decimal } from "./decimal.js";
13
+ import { DurationValue } from "./duration.js";
14
+ import { canonicalString, fromLexical } from "./lexical.js";
15
+ import { numberToDecimal } from "./numeric.js";
16
+ import { getType } from "./types.js";
17
+
18
+ const durationKinds = ["duration", "yearMonthDuration", "dayTimeDuration"];
19
+
20
+ /**
21
+ * @param {number} n
22
+ * @returns {number} n
23
+ * @throws {XPathError} FOCA0002 for NaN and the infinities
24
+ */
25
+ function finite(n) {
26
+ if (!Number.isFinite(n)) {
27
+ throw new XPathError("FOCA0002", `Cannot cast ${n} to a decimal`);
28
+ }
29
+ return n;
30
+ }
31
+
32
+ /** @param {*} v @param {string} from @returns {number} numeric value as a double */
33
+ function toDouble(v, from) {
34
+ if (from === "boolean") return v ? 1 : 0;
35
+ return from === "decimal" ? v.toNumber() : Number(v);
36
+ }
37
+
38
+ /**
39
+ * Projects a date/time value onto the components of another kind; missing
40
+ * time components become midnight (xs:date to xs:dateTime).
41
+ * @param {DateTimeValue} v
42
+ * @param {string} kind - Target primitive
43
+ * @returns {DateTimeValue}
44
+ */
45
+ function project(v, kind) {
46
+ const fields = { timezone: v.timezone };
47
+ for (const name of componentsOf(kind)) {
48
+ fields[name] = v[name] ?? (name === "second" ? Decimal.ZERO : 0);
49
+ }
50
+ return new DateTimeValue(fields);
51
+ }
52
+
53
+ /**
54
+ * Conversions of the F&O 3.1 19.1 table, by target then source primitive
55
+ * (the "Y" and "M" cells; strings are handled by lexical parsing).
56
+ */
57
+ const table = {
58
+ float: { double: Math.fround },
59
+ double: { float: (v) => v },
60
+ decimal: {
61
+ float: (v) => numberToDecimal(finite(v), true),
62
+ double: (v) => numberToDecimal(finite(v)),
63
+ integer: (v) => Decimal.of(v),
64
+ boolean: (v) => (v ? Decimal.of(1n) : Decimal.ZERO),
65
+ },
66
+ integer: {
67
+ float: (v) => BigInt(Math.trunc(finite(v))),
68
+ double: (v) => BigInt(Math.trunc(finite(v))),
69
+ decimal: (v) => v.trunc(),
70
+ boolean: (v) => (v ? 1n : 0n),
71
+ },
72
+ boolean: {
73
+ float: (v) => !(v === 0 || Number.isNaN(v)),
74
+ double: (v) => !(v === 0 || Number.isNaN(v)),
75
+ decimal: (v) => v.sign() !== 0,
76
+ integer: (v) => v !== 0n,
77
+ },
78
+ duration: {},
79
+ yearMonthDuration: {},
80
+ dayTimeDuration: {},
81
+ hexBinary: { base64Binary: (v) => v },
82
+ base64Binary: { hexBinary: (v) => v },
83
+ dateTime: { date: (v) => project(v, "dateTime") },
84
+ time: { dateTime: (v) => project(v, "time") },
85
+ date: { dateTime: (v) => project(v, "date") },
86
+ };
87
+ for (const from of ["integer", "decimal", "boolean"]) {
88
+ table.float[from] = (v) => Math.fround(toDouble(v, from));
89
+ table.double[from] = (v) => toDouble(v, from);
90
+ }
91
+ for (const to of durationKinds) {
92
+ for (const from of durationKinds) {
93
+ table[to][from] = (v) =>
94
+ new DurationValue(
95
+ to === "dayTimeDuration" ? 0 : v.months,
96
+ to === "yearMonthDuration" ? Decimal.ZERO : v.seconds,
97
+ );
98
+ }
99
+ }
100
+ for (const to of ["gYearMonth", "gYear", "gMonthDay", "gDay", "gMonth"]) {
101
+ table[to] = { dateTime: (v) => project(v, to), date: (v) => project(v, to) };
102
+ }
103
+
104
+ /**
105
+ * Converts a value between two primitive types of the casting table.
106
+ * @param {AtomicValue} item
107
+ * @param {object} target - Primitive type descriptor (F&O sense)
108
+ * @returns {*} the converted value representation
109
+ * @throws {XPathError} XPTY0004 when the table has no conversion
110
+ */
111
+ function convertPrimitive(item, target) {
112
+ const from = item.type.castPrimitive.localName;
113
+ if (from === target.localName) return item.value;
114
+ if (target.localName === "string" || target.localName === "untypedAtomic") {
115
+ return canonicalString(item);
116
+ }
117
+ const convert = table[target.localName]?.[from];
118
+ if (!convert) {
119
+ throw new XPathError(
120
+ "XPTY0004",
121
+ `Cannot cast ${item.type.prefixedName} to ${target.prefixedName}`,
122
+ );
123
+ }
124
+ return convert(item.value);
125
+ }
126
+
127
+ /**
128
+ * Casts an atomic value to an atomic type (`$value cast as T`).
129
+ * @param {AtomicValue} item
130
+ * @param {string|object} typeName - Target type name or descriptor
131
+ * @param {object} [options]
132
+ * @param {import("./qname.js").NamespaceResolver} [options.resolveNamespace]
133
+ * - Prefix resolver for casting strings to xs:QName
134
+ * @returns {AtomicValue}
135
+ * @throws {XPathError} XPST0080 (abstract target), XPTY0004 (cast not
136
+ * allowed), FORG0001 (invalid value; always for xs:error), FOCA0002 (NaN/INF to decimal or
137
+ * integer), FODT0001/FODT0002 (date/duration overflow), FONS0004
138
+ */
139
+ export function cast(item, typeName, options = {}) {
140
+ const target = getType(typeName);
141
+ if (target.abstract) {
142
+ throw new XPathError("XPST0080", `Cannot cast to ${target.prefixedName}`);
143
+ }
144
+ if (target.localName === "error") {
145
+ throw new XPathError("FORG0001", "No value can be cast to xs:error");
146
+ }
147
+ if (item.type === target) return item;
148
+ const sourcePrimitive = item.type.primitive.localName;
149
+ if (sourcePrimitive === "string" || sourcePrimitive === "untypedAtomic") {
150
+ return fromLexical(target, item.value, options);
151
+ }
152
+ const value = convertPrimitive(item, target.castPrimitive);
153
+ if (target.castPrimitive.localName === "string") {
154
+ return fromLexical(target, value);
155
+ }
156
+ return new AtomicValue(target, checkFacets(target, value));
157
+ }
158
+
159
+ /**
160
+ * Whether a cast would succeed (`$value castable as T`). Static errors
161
+ * (unknown or abstract target type) and non-XPath errors are still raised.
162
+ * @param {AtomicValue} item
163
+ * @param {string|object} typeName
164
+ * @param {object} [options] - As for {@link cast}
165
+ * @returns {boolean}
166
+ */
167
+ export function castable(item, typeName, options = {}) {
168
+ try {
169
+ cast(item, typeName, options);
170
+ return true;
171
+ } catch (error) {
172
+ const isStatic = error.code === "XPST0080" || error.code === "XPST0051";
173
+ if (!(error instanceof XPathError) || isStatic) throw error;
174
+ return false;
175
+ }
176
+ }
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Value comparisons of atomic values (XPath 3.1 section 3.7.1, operator
3
+ * mapping of appendix B.2, F&O 3.1 comparison operators) and the
4
+ * atomic-value equality of fn:deep-equal.
5
+ *
6
+ * @module @tradik/xslt3/xdm/compare
7
+ */
8
+
9
+ import { XPathError } from "../errors.js";
10
+ import { compareBytes } from "./binary.js";
11
+ import { timelineSeconds } from "./timeline.js";
12
+ import { Decimal } from "./decimal.js";
13
+ import { compareCodepoints } from "./strings.js";
14
+ import { derivesFrom } from "./types.js";
15
+
16
+ /**
17
+ * Options of the comparison functions.
18
+ * @typedef {object} CompareOptions
19
+ * @property {number} [implicitTimezone=0] - Offset in minutes applied to
20
+ * dates and times without a timezone
21
+ * @property {(a: string, b: string) => number} [collation] - String
22
+ * comparison; defaults to the Unicode codepoint collation
23
+ */
24
+
25
+ const numericRank = { decimal: 0, float: 1, double: 2 };
26
+ const stringLike = new Set(["string", "anyURI", "untypedAtomic"]);
27
+ const orderedTimes = new Set(["dateTime", "date", "time"]);
28
+ const unorderedTimes = new Set([
29
+ "gYearMonth",
30
+ "gYear",
31
+ "gMonthDay",
32
+ "gDay",
33
+ "gMonth",
34
+ ]);
35
+
36
+ /** @param {number} n @returns {number} sign of a difference, NaN kept */
37
+ const sign = (n) => (Number.isNaN(n) ? NaN : Math.sign(n));
38
+
39
+ /**
40
+ * Numeric value of an operand at a promotion level.
41
+ * @param {import("./atomic.js").AtomicValue} item
42
+ * @param {string} level - "decimal", "float" or "double"
43
+ * @returns {number|Decimal}
44
+ */
45
+ function promote(item, level) {
46
+ const { value } = item;
47
+ if (level === "decimal") {
48
+ return typeof value === "bigint" ? Decimal.of(value) : value;
49
+ }
50
+ const number = typeof value === "number" ? value : Number(value.toString());
51
+ return level === "float" ? Math.fround(number) : number;
52
+ }
53
+
54
+ /**
55
+ * Compares two numeric values with type promotion (decimal → float →
56
+ * double).
57
+ * @returns {number} -1, 0, 1 or NaN when unordered
58
+ */
59
+ function compareNumeric(a, b, pa, pb) {
60
+ if (typeof a.value === "bigint" && typeof b.value === "bigint") {
61
+ return a.value < b.value ? -1 : a.value > b.value ? 1 : 0;
62
+ }
63
+ const level = numericRank[pa] > numericRank[pb] ? pa : pb;
64
+ const x = promote(a, level);
65
+ const y = promote(b, level);
66
+ if (level === "decimal") return x.compare(y);
67
+ // Equal infinities: INF - INF is NaN, which would make them unordered
68
+ return x === y ? 0 : sign(x - y);
69
+ }
70
+
71
+ /**
72
+ * Compares two durations; ordering is only defined within
73
+ * xs:yearMonthDuration or within xs:dayTimeDuration.
74
+ * @returns {{order: number, ordered: boolean}}
75
+ */
76
+ function compareDurations(a, b) {
77
+ const yearMonth =
78
+ derivesFrom(a.type, "yearMonthDuration") &&
79
+ derivesFrom(b.type, "yearMonthDuration");
80
+ const dayTime =
81
+ derivesFrom(a.type, "dayTimeDuration") &&
82
+ derivesFrom(b.type, "dayTimeDuration");
83
+ const months = Math.sign(a.value.months - b.value.months);
84
+ const seconds = a.value.seconds.compare(b.value.seconds);
85
+ if (yearMonth) return { order: months, ordered: true };
86
+ if (dayTime) return { order: seconds, ordered: true };
87
+ return { order: months === 0 && seconds === 0 ? 0 : NaN, ordered: false };
88
+ }
89
+
90
+ /**
91
+ * Compares two atomic values per the operator mapping.
92
+ * @param {import("./atomic.js").AtomicValue} a
93
+ * @param {import("./atomic.js").AtomicValue} b
94
+ * @param {CompareOptions} [options]
95
+ * @returns {{order: number, ordered: boolean}} `order` is -1, 0, 1, or NaN
96
+ * when the values are unequal and unordered (NaN operands, mixed
97
+ * durations); `ordered` tells whether lt/gt are defined for the pair
98
+ * @throws {XPathError} XPTY0004 when the types are not comparable
99
+ */
100
+ export function compareAtomic(a, b, options = {}) {
101
+ const pa = a.type.primitive.localName;
102
+ const pb = b.type.primitive.localName;
103
+ if (pa in numericRank && pb in numericRank) {
104
+ return { order: compareNumeric(a, b, pa, pb), ordered: true };
105
+ }
106
+ if (stringLike.has(pa) && stringLike.has(pb)) {
107
+ const order = (options.collation ?? compareCodepoints)(a.value, b.value);
108
+ return { order: Math.sign(order), ordered: true };
109
+ }
110
+ if (pa !== pb) {
111
+ throw new XPathError(
112
+ "XPTY0004",
113
+ `Cannot compare ${a.type.prefixedName} with ${b.type.prefixedName}`,
114
+ );
115
+ }
116
+ if (pa === "boolean") {
117
+ return { order: Number(a.value) - Number(b.value), ordered: true };
118
+ }
119
+ if (pa === "duration") return compareDurations(a, b);
120
+ if (orderedTimes.has(pa) || unorderedTimes.has(pa)) {
121
+ const timezone = options.implicitTimezone ?? 0;
122
+ const order = timelineSeconds(a.value, timezone).compare(
123
+ timelineSeconds(b.value, timezone),
124
+ );
125
+ return { order, ordered: orderedTimes.has(pa) };
126
+ }
127
+ if (pa === "hexBinary" || pa === "base64Binary") {
128
+ return { order: compareBytes(a.value, b.value), ordered: true };
129
+ }
130
+ // xs:QName and xs:NOTATION: equality of namespace URI and local name
131
+ const equal =
132
+ a.value.namespaceURI === b.value.namespaceURI &&
133
+ a.value.localName === b.value.localName;
134
+ return { order: equal ? 0 : NaN, ordered: false };
135
+ }
136
+
137
+ /** Value comparison operators applied to an order (-1, 0, 1, NaN). */
138
+ export const orderTests = {
139
+ eq: (order) => order === 0,
140
+ ne: (order) => order !== 0,
141
+ lt: (order) => order < 0,
142
+ le: (order) => order <= 0,
143
+ gt: (order) => order > 0,
144
+ ge: (order) => order >= 0,
145
+ };
146
+
147
+ /**
148
+ * Value comparison `a op b` of two atomic values (xs:untypedAtomic is
149
+ * compared as xs:string). Empty and multi-item operands are the
150
+ * evaluator's concern.
151
+ * @param {import("./atomic.js").AtomicValue} a
152
+ * @param {"eq"|"ne"|"lt"|"le"|"gt"|"ge"} op
153
+ * @param {import("./atomic.js").AtomicValue} b
154
+ * @param {CompareOptions} [options]
155
+ * @returns {boolean}
156
+ * @throws {XPathError} XPTY0004 when the types are not comparable with op
157
+ */
158
+ export function valueCompare(a, op, b, options = {}) {
159
+ const { order, ordered } = compareAtomic(a, b, options);
160
+ if (!ordered && op !== "eq" && op !== "ne") {
161
+ throw new XPathError(
162
+ "XPTY0004",
163
+ `${op} is not defined for ${a.type.prefixedName}`,
164
+ );
165
+ }
166
+ return orderTests[op](order);
167
+ }
168
+
169
+ /**
170
+ * Equality of two atomic values as in fn:deep-equal and
171
+ * fn:distinct-values: incomparable types are unequal (no error) and NaN
172
+ * equals NaN.
173
+ * @param {import("./atomic.js").AtomicValue} a
174
+ * @param {import("./atomic.js").AtomicValue} b
175
+ * @param {CompareOptions} [options]
176
+ * @returns {boolean}
177
+ */
178
+ export function deepEqualAtomic(a, b, options = {}) {
179
+ let order;
180
+ try {
181
+ ({ order } = compareAtomic(a, b, options));
182
+ } catch (error) {
183
+ if (!(error instanceof XPathError)) throw error;
184
+ return false;
185
+ }
186
+ return order === 0 || (Number.isNaN(order) && isNaNValue(a) && isNaNValue(b));
187
+ }
188
+
189
+ /** @param {import("./atomic.js").AtomicValue} item @returns {boolean} */
190
+ function isNaNValue(item) {
191
+ return typeof item.value === "number" && Number.isNaN(item.value);
192
+ }