@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,203 @@
1
+ /**
2
+ * Functions on durations, dates and times (F&O 3.1 sections 8.3, 9.3 to
3
+ * 9.5, and the context functions of 16.1): component extraction,
4
+ * fn:dateTime, timezone adjustment, current-dateTime/date/time and
5
+ * implicit-timezone.
6
+ *
7
+ * @module @tradik/xslt3/functions/datetime
8
+ */
9
+
10
+ import { XPathError } from "../errors.js";
11
+ import { AtomicValue } from "../xdm/atomic.js";
12
+ import { componentsOf, DateTimeValue } from "../xdm/datetime.js";
13
+ import { Decimal } from "../xdm/decimal.js";
14
+ import { DurationValue } from "../xdm/duration.js";
15
+ import { fromLocalSeconds, localSeconds } from "../xdm/timeline.js";
16
+ import { types } from "../xdm/types.js";
17
+ import { decimalItem, define, integerItem } from "./support.js";
18
+
19
+ /** @param {number} minutes @returns {AtomicValue} an xs:dayTimeDuration */
20
+ export const timezoneDuration = (minutes) =>
21
+ new AtomicValue(
22
+ types.dayTimeDuration,
23
+ new DurationValue(0, Decimal.of(BigInt(minutes * 60))),
24
+ );
25
+
26
+ /**
27
+ * Replaces the timezone of a date/time value, adjusting the local value
28
+ * when it had one (F&O 3.1 section 9.5).
29
+ * @param {AtomicValue} item - xs:dateTime, xs:date or xs:time
30
+ * @param {number|null} timezone - Minutes, null to remove the timezone
31
+ * @returns {AtomicValue}
32
+ */
33
+ export function adjustToTimezone(item, timezone) {
34
+ const kind = item.type.primitive.localName;
35
+ const { value } = item;
36
+ let local = value;
37
+ if (value.timezone !== null && timezone !== null) {
38
+ const shift = Decimal.of(BigInt((timezone - value.timezone) * 60));
39
+ local = fromLocalSeconds(localSeconds(value).add(shift), timezone);
40
+ }
41
+ const fields = { timezone };
42
+ for (const name of componentsOf(kind)) fields[name] = local[name];
43
+ return new AtomicValue(types[kind], new DateTimeValue(fields));
44
+ }
45
+
46
+ /**
47
+ * The timezone argument of the adjust functions in minutes.
48
+ * @param {Array<AtomicValue>|undefined} sequence - Undefined: the implicit
49
+ * timezone; empty: no timezone
50
+ * @param {object} context
51
+ * @returns {number|null}
52
+ * @throws {XPathError} FODT0003 for an invalid timezone
53
+ */
54
+ function timezoneArg(sequence, context) {
55
+ if (sequence === undefined) return context.implicitTimezone ?? 0;
56
+ if (sequence.length === 0) return null;
57
+ const seconds = sequence[0].value.seconds;
58
+ const minutes = seconds.div(Decimal.of(60n));
59
+ if (!minutes.isInteger() || Math.abs(minutes.toNumber()) > 14 * 60) {
60
+ throw new XPathError("FODT0003", `Invalid timezone ${seconds}S`);
61
+ }
62
+ return minutes.toNumber();
63
+ }
64
+
65
+ /**
66
+ * Declares a function of one optional argument returning one optional
67
+ * value.
68
+ * @param {string} local
69
+ * @param {string} param
70
+ * @param {string} returns
71
+ * @param {(value: *) => AtomicValue|null} extract
72
+ * @returns {import("./support.js").FunctionDefinition}
73
+ */
74
+ const accessor = (local, param, returns, extract) =>
75
+ define(local, [`${param}?`], `${returns}?`, ([arg]) => {
76
+ const result = arg.length === 0 ? null : extract(arg[0].value);
77
+ return result === null ? [] : [result];
78
+ });
79
+
80
+ const component = (name) => (value) =>
81
+ name === "second" ? decimalItem(value.second) : integerItem(value[name]);
82
+ const timezoneOf = (value) =>
83
+ value.timezone === null ? null : timezoneDuration(value.timezone);
84
+
85
+ /** Component accessors by type: [type name, [function suffix, component]] */
86
+ const extractors = [
87
+ ["dateTime", ["year", "month", "day", "hours", "minutes", "seconds"]],
88
+ ["date", ["year", "month", "day"]],
89
+ ["time", ["hours", "minutes", "seconds"]],
90
+ ];
91
+ const componentNames = {
92
+ year: "year",
93
+ month: "month",
94
+ day: "day",
95
+ hours: "hour",
96
+ minutes: "minute",
97
+ seconds: "second",
98
+ };
99
+
100
+ const DAY = 86400n;
101
+ /** Duration component extractors on (months, whole seconds, seconds). */
102
+ const durationParts = {
103
+ years: (d) => integerItem(Math.trunc(d.months / 12)),
104
+ months: (d) => integerItem(d.months % 12),
105
+ days: (d) => integerItem(d.seconds.trunc() / DAY),
106
+ hours: (d) => integerItem((d.seconds.trunc() / 3600n) % 24n),
107
+ minutes: (d) => integerItem((d.seconds.trunc() / 60n) % 60n),
108
+ seconds: (d) => decimalItem(d.seconds.mod(Decimal.of(60n))),
109
+ };
110
+
111
+ /**
112
+ * The current date/time from the context, with the implicit timezone when
113
+ * it has none, projected on a type.
114
+ * @param {object} context
115
+ * @param {string} kind - "dateTime", "date" or "time"
116
+ * @returns {AtomicValue}
117
+ */
118
+ function current(context, kind) {
119
+ const now = context.currentDateTime;
120
+ const fields = { timezone: now.timezone ?? context.implicitTimezone ?? 0 };
121
+ for (const name of componentsOf(kind)) fields[name] = now[name];
122
+ const type = kind === "dateTime" ? types.dateTimeStamp : types[kind];
123
+ return new AtomicValue(type, new DateTimeValue(fields));
124
+ }
125
+
126
+ /**
127
+ * fn:dateTime.
128
+ * @param {Array<AtomicValue>[]} args - [$date, $time]
129
+ * @returns {Array<AtomicValue>}
130
+ */
131
+ function dateTime([date, time]) {
132
+ if (date.length === 0 || time.length === 0) return [];
133
+ const d = date[0].value;
134
+ const t = time[0].value;
135
+ if (d.timezone !== null && t.timezone !== null && d.timezone !== t.timezone) {
136
+ throw new XPathError("FORG0008", "The date and time timezones differ");
137
+ }
138
+ const value = new DateTimeValue({
139
+ ...t,
140
+ year: d.year,
141
+ month: d.month,
142
+ day: d.day,
143
+ timezone: d.timezone ?? t.timezone,
144
+ });
145
+ return [new AtomicValue(types.dateTime, value)];
146
+ }
147
+
148
+ /** @type {import("./support.js").FunctionDefinition[]} */
149
+ export const dateTimeFunctions = [
150
+ ...extractors.flatMap(([type, parts]) => [
151
+ ...parts.map((part) =>
152
+ accessor(
153
+ `${part}-from-${type}`,
154
+ `xs:${type}`,
155
+ part === "seconds" ? "xs:decimal" : "xs:integer",
156
+ component(componentNames[part]),
157
+ ),
158
+ ),
159
+ accessor(
160
+ `timezone-from-${type}`,
161
+ `xs:${type}`,
162
+ "xs:dayTimeDuration",
163
+ timezoneOf,
164
+ ),
165
+ ]),
166
+ ...Object.entries(durationParts).map(([part, extract]) =>
167
+ accessor(
168
+ `${part}-from-duration`,
169
+ "xs:duration",
170
+ part === "seconds" ? "xs:decimal" : "xs:integer",
171
+ extract,
172
+ ),
173
+ ),
174
+ define("dateTime", ["xs:date?", "xs:time?"], "xs:dateTime?", dateTime),
175
+ ...["dateTime", "date", "time"].flatMap((type) => {
176
+ const impl = ([arg, timezone], context) =>
177
+ arg.length === 0
178
+ ? []
179
+ : [adjustToTimezone(arg[0], timezoneArg(timezone, context))];
180
+ const local = `adjust-${type}-to-timezone`;
181
+ return [
182
+ define(local, [`xs:${type}?`], `xs:${type}?`, impl),
183
+ define(
184
+ local,
185
+ [`xs:${type}?`, "xs:dayTimeDuration?"],
186
+ `xs:${type}?`,
187
+ impl,
188
+ ),
189
+ ];
190
+ }),
191
+ define("current-dateTime", [], "xs:dateTimeStamp", (_a, context) => [
192
+ current(context, "dateTime"),
193
+ ]),
194
+ define("current-date", [], "xs:date", (_a, context) => [
195
+ current(context, "date"),
196
+ ]),
197
+ define("current-time", [], "xs:time", (_a, context) => [
198
+ current(context, "time"),
199
+ ]),
200
+ define("implicit-timezone", [], "xs:dayTimeDuration", (_a, context) => [
201
+ timezoneDuration(context.implicitTimezone ?? 0),
202
+ ]),
203
+ ];
@@ -0,0 +1,132 @@
1
+ /**
2
+ * fn:deep-equal (F&O 3.1 section 14.2.1) for atomic values and DOM nodes
3
+ * of any implementation (nodeType, namespaceURI, localName, nodeName,
4
+ * nodeValue, attributes and childNodes are read).
5
+ *
6
+ * Maps, arrays and function items are delegated to the hook
7
+ * `options.deepEqualItem(a, b, options)`, which the evaluator supplies
8
+ * through the dynamic context (`context.deepEqualItem`) because it owns
9
+ * their representation. Without the hook such items raise FOTY0015.
10
+ *
11
+ * @module @tradik/xslt3/functions/deepEqual
12
+ */
13
+
14
+ import { XPathError } from "../errors.js";
15
+ import { deepEqualAtomic } from "../xdm/compare.js";
16
+ import { isAtomic, isNode } from "../xdm/atomic.js";
17
+ import { nodeStringValue } from "../xdm/nodes.js";
18
+
19
+ const XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/";
20
+ const ELEMENT = 1;
21
+ const ATTRIBUTE = 2;
22
+ const PROCESSING_INSTRUCTION = 7;
23
+ const COMMENT = 8;
24
+ const DOCUMENT = 9;
25
+ const DOCUMENT_FRAGMENT = 11;
26
+
27
+ /**
28
+ * @typedef {object} DeepEqualOptions
29
+ * @property {(a: string, b: string) => number} collation
30
+ * @property {number} implicitTimezone
31
+ * @property {(a: *, b: *, options: DeepEqualOptions) => boolean} [deepEqualItem]
32
+ */
33
+
34
+ /** @param {Node} node @returns {string} "{uri}local" */
35
+ const expandedName = (node) =>
36
+ `{${node.namespaceURI ?? ""}}${node.localName ?? node.nodeName}`;
37
+
38
+ /** @param {Node} node @returns {Node[]} children without comments and PIs */
39
+ const significantChildren = (node) =>
40
+ Array.from(node.childNodes).filter(
41
+ (child) =>
42
+ child.nodeType !== COMMENT && child.nodeType !== PROCESSING_INSTRUCTION,
43
+ );
44
+
45
+ /** @param {Element} node @returns {Attr[]} attributes without namespace declarations */
46
+ const realAttributes = (node) =>
47
+ Array.from(node.attributes ?? []).filter(
48
+ (attr) => attr.namespaceURI !== XMLNS_NAMESPACE,
49
+ );
50
+
51
+ /**
52
+ * @param {Node} a
53
+ * @param {Node} b
54
+ * @param {DeepEqualOptions} options
55
+ * @returns {boolean} whether two nodes are deep-equal
56
+ */
57
+ export function deepEqualNodes(a, b, options) {
58
+ const kind = (node) =>
59
+ node.nodeType === DOCUMENT_FRAGMENT ? DOCUMENT : node.nodeType;
60
+ if (kind(a) !== kind(b)) return false;
61
+ const sameText = () =>
62
+ options.collation(nodeStringValue(a), nodeStringValue(b)) === 0;
63
+ switch (kind(a)) {
64
+ case DOCUMENT:
65
+ return childrenEqual(a, b, options);
66
+ case ELEMENT: {
67
+ if (expandedName(a) !== expandedName(b)) return false;
68
+ const attributes = realAttributes(a);
69
+ const others = realAttributes(b);
70
+ return (
71
+ attributes.length === others.length &&
72
+ attributes.every((attr) =>
73
+ others.some((other) => deepEqualNodes(attr, other, options)),
74
+ ) &&
75
+ childrenEqual(a, b, options)
76
+ );
77
+ }
78
+ case ATTRIBUTE:
79
+ case PROCESSING_INSTRUCTION:
80
+ return expandedName(a) === expandedName(b) && sameText();
81
+ default:
82
+ // text, comment and namespace nodes
83
+ return sameText();
84
+ }
85
+ }
86
+
87
+ /**
88
+ * @param {Node} a
89
+ * @param {Node} b
90
+ * @param {DeepEqualOptions} options
91
+ * @returns {boolean} whether the significant children are pairwise equal
92
+ */
93
+ function childrenEqual(a, b, options) {
94
+ const x = significantChildren(a);
95
+ const y = significantChildren(b);
96
+ return (
97
+ x.length === y.length &&
98
+ x.every((child, i) => deepEqualNodes(child, y[i], options))
99
+ );
100
+ }
101
+
102
+ /**
103
+ * @param {*} a
104
+ * @param {*} b
105
+ * @param {DeepEqualOptions} options
106
+ * @returns {boolean} whether two items are deep-equal
107
+ * @throws {XPathError} FOTY0015 for function items without the hook
108
+ */
109
+ function deepEqualItems(a, b, options) {
110
+ if (isAtomic(a) || isAtomic(b)) {
111
+ return isAtomic(a) && isAtomic(b) && deepEqualAtomic(a, b, options);
112
+ }
113
+ if (isNode(a) || isNode(b)) {
114
+ return isNode(a) && isNode(b) && deepEqualNodes(a, b, options);
115
+ }
116
+ if (options.deepEqualItem) return options.deepEqualItem(a, b, options);
117
+ throw new XPathError("FOTY0015", "deep-equal of function items");
118
+ }
119
+
120
+ /**
121
+ * Whether two sequences are deep-equal.
122
+ * @param {Array<*>} a
123
+ * @param {Array<*>} b
124
+ * @param {DeepEqualOptions} options
125
+ * @returns {boolean}
126
+ */
127
+ export function deepEqualSequences(a, b, options) {
128
+ return (
129
+ a.length === b.length &&
130
+ a.every((item, i) => deepEqualItems(item, b[i], options))
131
+ );
132
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Error and diagnostic functions (F&O 3.1 sections 3 and 4): fn:error and
3
+ * fn:trace, and the document functions fn:doc and fn:doc-available.
4
+ *
5
+ * @module @tradik/xslt3/functions/diagnostics
6
+ */
7
+
8
+ import { XPathError } from "../errors.js";
9
+ import { booleanItem } from "../xpath/eval/atomics.js";
10
+
11
+ /** Error code of an xs:QName: the local name in the err namespace. */
12
+ function errorCode(qname) {
13
+ if (qname.namespaceURI === "http://www.w3.org/2005/xqt-errors") {
14
+ return qname.localName;
15
+ }
16
+ return `Q{${qname.namespaceURI}}${qname.localName}`;
17
+ }
18
+
19
+ /**
20
+ * fn:error.
21
+ * @param {Array<Array>} args
22
+ * @returns {never}
23
+ */
24
+ function raise([code = [], description, object]) {
25
+ const qname = code[0]?.value;
26
+ const error = new XPathError(
27
+ qname ? errorCode(qname) : "FOER0000",
28
+ description?.[0]?.value ?? "Error raised by fn:error",
29
+ );
30
+ error.qname = qname;
31
+ error.errorObject = object;
32
+ throw error;
33
+ }
34
+
35
+ /** Function definitions. */
36
+ export const diagnosticFunctions = [
37
+ { local: "error", params: [], returns: "item()*", impl: raise },
38
+ { local: "error", params: ["xs:QName?"], returns: "item()*", impl: raise },
39
+ {
40
+ local: "error",
41
+ params: ["xs:QName?", "xs:string"],
42
+ returns: "item()*",
43
+ impl: raise,
44
+ },
45
+ {
46
+ local: "error",
47
+ params: ["xs:QName?", "xs:string", "item()*"],
48
+ returns: "item()*",
49
+ impl: raise,
50
+ },
51
+ {
52
+ local: "trace",
53
+ params: ["item()*"],
54
+ returns: "item()*",
55
+ impl: ([value], context) => (context.trace(value, ""), value),
56
+ },
57
+ {
58
+ local: "trace",
59
+ params: ["item()*", "xs:string"],
60
+ returns: "item()*",
61
+ impl: ([value, [label]], context) => (
62
+ context.trace(value, label.value),
63
+ value
64
+ ),
65
+ },
66
+ {
67
+ local: "doc",
68
+ params: ["xs:string?"],
69
+ returns: "document-node()?",
70
+ impl: ([uri], context) =>
71
+ uri.length ? [context.loadDocument(uri[0].value)] : [],
72
+ },
73
+ {
74
+ local: "doc-available",
75
+ params: ["xs:string?"],
76
+ returns: "xs:boolean",
77
+ impl: ([uri], context) => {
78
+ if (!uri.length) return [booleanItem(false)];
79
+ try {
80
+ context.loadDocument(uri[0].value);
81
+ return [booleanItem(true)];
82
+ } catch {
83
+ // loadDocument raises only FODC0002 and FODC0005
84
+ return [booleanItem(false)];
85
+ }
86
+ },
87
+ },
88
+ ];
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Access to the focus from function implementations (definitions with
3
+ * `focus: true`).
4
+ *
5
+ * @module @tradik/xslt3/functions/focus
6
+ */
7
+
8
+ import { XPathError } from "../errors.js";
9
+ import { isNode } from "../xdm/atomic.js";
10
+
11
+ /**
12
+ * @param {import("./registry.js").DynamicContext} context
13
+ * @returns {*} the context item
14
+ * @throws {XPathError} XPDY0002 when the focus is absent
15
+ */
16
+ export function focusItem(context) {
17
+ if (context.contextItem === undefined) {
18
+ throw new XPathError("XPDY0002", "The context item is absent");
19
+ }
20
+ return context.contextItem;
21
+ }
22
+
23
+ /**
24
+ * @param {import("./registry.js").DynamicContext} context
25
+ * @returns {Node} the context item, which must be a node
26
+ * @throws {XPathError} XPDY0002 when absent, XPTY0004 when not a node
27
+ */
28
+ export function focusNode(context) {
29
+ const item = focusItem(context);
30
+ if (!isNode(item)) {
31
+ throw new XPathError("XPTY0004", "The context item is not a node");
32
+ }
33
+ return item;
34
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Values and English names of the date/time components used by the
3
+ * format-dateTime family (F&O 3.1 section 9.8.4.1): day of year, ISO day
4
+ * of week and week numbers, 12-hour clock, names of months, days, am/pm
5
+ * and eras.
6
+ *
7
+ * @module @tradik/xslt3/functions/format/dateComponents
8
+ */
9
+
10
+ import { civilFromDays, daysFromCivil } from "../../xdm/calendar.js";
11
+
12
+ const MONTHS = (
13
+ "January February March April May June July August September October " +
14
+ "November December"
15
+ ).split(" ");
16
+ const DAYS = "Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(
17
+ " ",
18
+ );
19
+
20
+ /**
21
+ * Conventional English abbreviations longer than three letters, by name;
22
+ * every name of more than three letters also abbreviates to its first
23
+ * three.
24
+ */
25
+ const ABBREVIATIONS = {
26
+ Tuesday: ["Tues"],
27
+ Wednesday: ["Weds"],
28
+ Thursday: ["Thur", "Thurs"],
29
+ September: ["Sept"],
30
+ };
31
+
32
+ /**
33
+ * The longest abbreviation of a name that fits a maximum width, else the
34
+ * name truncated to it.
35
+ * @param {string} name - Capitalized English name
36
+ * @param {number} maxWidth
37
+ * @returns {string}
38
+ */
39
+ function abbreviate(name, maxWidth) {
40
+ const chars = Array.from(name);
41
+ if (chars.length <= maxWidth) return name;
42
+ const candidates = [name.slice(0, 3), ...(ABBREVIATIONS[name] ?? [])];
43
+ const fitting = candidates.filter(
44
+ (c) => c.length <= maxWidth && c.length < chars.length,
45
+ );
46
+ return fitting.length
47
+ ? fitting[fitting.length - 1]
48
+ : chars.slice(0, maxWidth).join("");
49
+ }
50
+
51
+ /** Components available per value kind. */
52
+ const AVAILABLE = {
53
+ dateTime: "YMDdFWwHhPmsfZzCE",
54
+ date: "YMDdFWwZzCE",
55
+ time: "HhPmsfZzC",
56
+ };
57
+
58
+ /**
59
+ * @param {string} kind - "dateTime", "date" or "time"
60
+ * @param {string} component
61
+ * @returns {boolean} whether the component exists in values of the kind
62
+ */
63
+ export const isAvailable = (kind, component) =>
64
+ AVAILABLE[kind].includes(component);
65
+
66
+ /**
67
+ * ISO day of the week, 1 (Monday) to 7 (Sunday), of a day number.
68
+ * @param {number} days - Days since 1970-01-01 (a Thursday)
69
+ * @returns {number}
70
+ */
71
+ const isoDayOfWeek = (days) => ((((days + 3) % 7) + 7) % 7) + 1;
72
+
73
+ /**
74
+ * Week numbers of a date: ISO week in year, and week in month where a
75
+ * Monday-to-Sunday week belongs to the month of its Thursday, except that
76
+ * the last days of a month whose Thursday is in the next month continue
77
+ * the count of their own month (as Saxon and the W3C tests do).
78
+ * @param {import("../../xdm/datetime.js").DateTimeValue} value
79
+ * @returns {{week: number, weekInMonth: number}}
80
+ */
81
+ function weeks(value) {
82
+ const days = daysFromCivil(value.year, value.month, value.day);
83
+ const thursday = days - isoDayOfWeek(days) + 4;
84
+ const [year, , day] = civilFromDays(thursday);
85
+ const dayOfYear = thursday - daysFromCivil(year, 1, 1);
86
+ const monthStart = daysFromCivil(value.year, value.month, 1);
87
+ const dayInMonth = thursday >= monthStart ? thursday - monthStart + 1 : day;
88
+ return {
89
+ week: Math.floor(dayOfYear / 7) + 1,
90
+ weekInMonth: Math.floor((dayInMonth - 1) / 7) + 1,
91
+ };
92
+ }
93
+
94
+ /**
95
+ * The numeric value of a component (Y is the absolute year; s the whole
96
+ * seconds).
97
+ * @param {import("../../xdm/datetime.js").DateTimeValue} value
98
+ * @param {string} component - One of YMDdFWwHhms
99
+ * @returns {number}
100
+ */
101
+ export function componentValue(value, component) {
102
+ switch (component) {
103
+ case "Y":
104
+ return Math.abs(value.year);
105
+ case "M":
106
+ return value.month;
107
+ case "D":
108
+ return value.day;
109
+ case "d":
110
+ return (
111
+ daysFromCivil(value.year, value.month, value.day) -
112
+ daysFromCivil(value.year, 1, 1) +
113
+ 1
114
+ );
115
+ case "F":
116
+ return isoDayOfWeek(daysFromCivil(value.year, value.month, value.day));
117
+ case "W":
118
+ return weeks(value).week;
119
+ case "w":
120
+ return weeks(value).weekInMonth;
121
+ case "H":
122
+ return value.hour;
123
+ case "h":
124
+ return value.hour % 12 || 12;
125
+ case "m":
126
+ return value.minute;
127
+ default:
128
+ return Number(value.second.trunc());
129
+ }
130
+ }
131
+
132
+ /**
133
+ * The English name of a component, or null when it has none.
134
+ * @param {import("../../xdm/datetime.js").DateTimeValue} value
135
+ * @param {string} component
136
+ * @param {string} calendar - Calendar used, e.g. "AD"
137
+ * @returns {string|null}
138
+ */
139
+ export function componentName(value, component, calendar) {
140
+ switch (component) {
141
+ case "M":
142
+ return MONTHS[value.month - 1];
143
+ case "F":
144
+ return DAYS[componentValue(value, "F") - 1];
145
+ case "P":
146
+ return value.hour < 12 ? "am" : "pm";
147
+ case "C":
148
+ return calendar;
149
+ case "E":
150
+ if (calendar === "ISO") return value.year < 0 ? "-" : "";
151
+ return value.year > 0 ? "AD" : "BC";
152
+ default:
153
+ return null;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Applies a name presentation modifier and the width modifier to a name.
159
+ * @param {string} name
160
+ * @param {string} token - "n", "N" or "Nn"
161
+ * @param {number|null} minWidth
162
+ * @param {number|null} maxWidth
163
+ * @returns {string}
164
+ */
165
+ export function presentName(name, token, minWidth, maxWidth) {
166
+ const capitalized = name.charAt(0).toUpperCase() + name.slice(1);
167
+ const short =
168
+ maxWidth === null ? capitalized : abbreviate(capitalized, maxWidth);
169
+ const text =
170
+ token === "N"
171
+ ? short.toUpperCase()
172
+ : token === "n"
173
+ ? short.toLowerCase()
174
+ : short;
175
+ return minWidth === null ? text : text.padEnd(minWidth, " ");
176
+ }