@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,155 @@
1
+ /**
2
+ * Numbering in words and other systems for format-integer and
3
+ * format-date/time (F&O 3.1 sections 4.6.1 and 9.8.4): English cardinal
4
+ * and ordinal words, ordinal suffixes, Roman numerals and alphabetic
5
+ * sequences.
6
+ *
7
+ * @module @tradik/xslt3/functions/format/numberWords
8
+ */
9
+
10
+ const ONES = (
11
+ "zero one two three four five six seven eight nine ten eleven twelve " +
12
+ "thirteen fourteen fifteen sixteen seventeen eighteen nineteen"
13
+ ).split(" ");
14
+ const TENS = " twenty thirty forty fifty sixty seventy eighty ninety".split(
15
+ " ",
16
+ );
17
+ const SCALES = [
18
+ [10n ** 18n, "quintillion"],
19
+ [10n ** 15n, "quadrillion"],
20
+ [10n ** 12n, "trillion"],
21
+ [10n ** 9n, "billion"],
22
+ [10n ** 6n, "million"],
23
+ [1000n, "thousand"],
24
+ ];
25
+
26
+ /**
27
+ * English cardinal words, e.g. "one hundred and twenty-three".
28
+ * @param {bigint} n - Non-negative
29
+ * @returns {string}
30
+ */
31
+ export function cardinalWords(n) {
32
+ if (n < 20n) return ONES[Number(n)];
33
+ if (n < 100n) {
34
+ const units = n % 10n;
35
+ return TENS[Number(n / 10n)] + (units ? `-${ONES[Number(units)]}` : "");
36
+ }
37
+ if (n < 1000n) {
38
+ const rest = n % 100n;
39
+ return (
40
+ `${ONES[Number(n / 100n)]} hundred` +
41
+ (rest ? ` and ${cardinalWords(rest)}` : "")
42
+ );
43
+ }
44
+ const [scale, name] = SCALES.find(([s]) => n >= s) ?? SCALES[0];
45
+ const rest = n % scale;
46
+ const joiner = rest === 0n ? "" : rest < 100n ? " and " : " ";
47
+ return (
48
+ `${cardinalWords(n / scale)} ${name}` +
49
+ joiner +
50
+ (rest ? cardinalWords(rest) : "")
51
+ );
52
+ }
53
+
54
+ const IRREGULAR_ORDINALS = {
55
+ one: "first",
56
+ two: "second",
57
+ three: "third",
58
+ five: "fifth",
59
+ eight: "eighth",
60
+ nine: "ninth",
61
+ twelve: "twelfth",
62
+ };
63
+
64
+ /**
65
+ * English ordinal words, e.g. "twenty-first".
66
+ * @param {bigint} n - Non-negative
67
+ * @returns {string}
68
+ */
69
+ export function ordinalWords(n) {
70
+ const words = cardinalWords(n);
71
+ const [, head, last] = /^(.*?)([a-z]+)$/.exec(words);
72
+ const ordinal =
73
+ IRREGULAR_ORDINALS[last] ??
74
+ (last.endsWith("y") ? `${last.slice(0, -1)}ieth` : `${last}th`);
75
+ return head + ordinal;
76
+ }
77
+
78
+ /**
79
+ * English ordinal suffix of a number: "st", "nd", "rd" or "th".
80
+ * @param {bigint} n - Non-negative
81
+ * @returns {string}
82
+ */
83
+ export function ordinalSuffix(n) {
84
+ const lastTwo = n % 100n;
85
+ if (lastTwo >= 11n && lastTwo <= 13n) return "th";
86
+ return ["th", "st", "nd", "rd"][Number(n % 10n)] ?? "th";
87
+ }
88
+
89
+ const ROMAN = [
90
+ [1000n, "m"],
91
+ [900n, "cm"],
92
+ [500n, "d"],
93
+ [400n, "cd"],
94
+ [100n, "c"],
95
+ [90n, "xc"],
96
+ [50n, "l"],
97
+ [40n, "xl"],
98
+ [10n, "x"],
99
+ [9n, "ix"],
100
+ [5n, "v"],
101
+ [4n, "iv"],
102
+ [1n, "i"],
103
+ ];
104
+
105
+ /** Largest number written in Roman numerals; larger ones use digits. */
106
+ export const MAX_ROMAN = 4999n;
107
+
108
+ /**
109
+ * Lower-case Roman numerals.
110
+ * @param {bigint} n - 1 to {@link MAX_ROMAN}
111
+ * @returns {string}
112
+ */
113
+ export function roman(n) {
114
+ let result = "";
115
+ let rest = n;
116
+ for (const [value, letters] of ROMAN) {
117
+ while (rest >= value) {
118
+ result += letters;
119
+ rest -= value;
120
+ }
121
+ }
122
+ return result;
123
+ }
124
+
125
+ /**
126
+ * Alphabetic numbering a, b, ..., z, aa, ab, ... (bijective base 26).
127
+ * @param {bigint} n - Positive
128
+ * @param {string} first - "a" or "A"
129
+ * @returns {string}
130
+ */
131
+ export function alphabetic(n, first) {
132
+ const base = first.codePointAt(0);
133
+ let result = "";
134
+ for (let rest = n; rest > 0n; rest = (rest - 1n) / 26n) {
135
+ result = String.fromCodePoint(base + Number((rest - 1n) % 26n)) + result;
136
+ }
137
+ return result;
138
+ }
139
+
140
+ /**
141
+ * Applies the case of a word format token.
142
+ * @param {string} words - Lower-case words
143
+ * @param {string} token - "w", "W" or "Ww"
144
+ * @returns {string}
145
+ */
146
+ export function caseWords(words, token) {
147
+ if (token === "W") return words.toUpperCase();
148
+ if (token === "Ww") {
149
+ // title case, with "and" kept lower-case as in the F&O examples
150
+ return words.replace(/(?<!\p{L})(?!and(?!\p{L}))\p{Ll}/gu, (c) =>
151
+ c.toUpperCase(),
152
+ );
153
+ }
154
+ return words;
155
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The $place argument of the format-dateTime family (F&O 3.1 section
3
+ * 9.8.4.8) given as an IANA timezone name: the value is adjusted to the
4
+ * offset of that timezone at that instant (daylight saving included), and
5
+ * the timezone gets its conventional abbreviation for [ZN]. Uses the
6
+ * timezone database of `Intl.DateTimeFormat`. Country codes are ignored.
7
+ *
8
+ * @module @tradik/xslt3/functions/format/place
9
+ */
10
+
11
+ import { AtomicValue } from "../../xdm/atomic.js";
12
+ import { types } from "../../xdm/types.js";
13
+ import { adjustToTimezone } from "../datetime.js";
14
+
15
+ /**
16
+ * The name of the timezone of an instant in a locale's style.
17
+ * @param {string} timeZone
18
+ * @param {number} instant - Milliseconds since the epoch
19
+ * @param {string} locale
20
+ * @param {string} style - "longOffset" or "short"
21
+ * @returns {string}
22
+ */
23
+ function zoneName(timeZone, instant, locale, style) {
24
+ return new Intl.DateTimeFormat(locale, { timeZone, timeZoneName: style })
25
+ .formatToParts(instant)
26
+ .find((part) => part.type === "timeZoneName").value;
27
+ }
28
+
29
+ /**
30
+ * Adjusts a date or dateTime with a timezone to the timezone of a place.
31
+ * @param {string} kind - "dateTime", "date" or "time"
32
+ * @param {import("../../xdm/datetime.js").DateTimeValue} value
33
+ * @param {string|null|undefined} place
34
+ * @returns {{value: import("../../xdm/datetime.js").DateTimeValue,
35
+ * zone: string|null}} the value to format and the timezone abbreviation
36
+ * (null when the place is not a known IANA name or does not apply)
37
+ */
38
+ export function applyPlace(kind, value, place) {
39
+ const timeZone = place?.trim() ?? "";
40
+ if (!timeZone.includes("/") || kind === "time" || value.timezone === null) {
41
+ return { value, zone: null };
42
+ }
43
+ const instant =
44
+ Date.UTC(
45
+ value.year,
46
+ value.month - 1,
47
+ value.day,
48
+ value.hour ?? 0,
49
+ value.minute ?? 0,
50
+ ) -
51
+ value.timezone * 60000;
52
+ let offsetName;
53
+ let zone;
54
+ try {
55
+ offsetName = zoneName(timeZone, instant, "en-US", "longOffset");
56
+ // American names (EST) first, then British ones (CET, BST)
57
+ zone = ["en-US", "en-GB"]
58
+ .map((locale) => zoneName(timeZone, instant, locale, "short"))
59
+ .find((name) => !/^GMT[+-]/.test(name));
60
+ } catch {
61
+ return { value, zone: null };
62
+ }
63
+ const [, sign, hours, minutes] = /^GMT(?:([+-])(\d\d):(\d\d))?$/.exec(
64
+ offsetName,
65
+ );
66
+ const offset = sign
67
+ ? (sign === "-" ? -1 : 1) * (Number(hours) * 60 + Number(minutes))
68
+ : 0;
69
+ const adjusted = adjustToTimezone(
70
+ new AtomicValue(types[kind], value),
71
+ offset,
72
+ );
73
+ return { value: adjusted.value, zone: zone ?? null };
74
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Timezone components [Z] and [z] of the format-dateTime family (F&O 3.1
3
+ * section 9.8.4.6): numeric offsets with one to four digits and optional
4
+ * separator, "t" for Z at UTC, military letters ([ZZ]) and a few North
5
+ * American and UTC names ([ZN]).
6
+ *
7
+ * @module @tradik/xslt3/functions/format/timezoneFormat
8
+ */
9
+
10
+ import { zeroOf } from "./digitPattern.js";
11
+
12
+ const MILITARY = "YXWVUTSRQPONZABCDEFGHIKLM";
13
+ const NAMES = new Map([
14
+ [0, "GMT"],
15
+ [-300, "EST"],
16
+ [-360, "CST"],
17
+ [-420, "MST"],
18
+ [-480, "PST"],
19
+ [-600, "HST"],
20
+ ]);
21
+
22
+ /**
23
+ * Formats an offset with a numeric presentation modifier such as "01:01",
24
+ * "0", "0000".
25
+ * @param {number} offset - Minutes
26
+ * @param {string} token
27
+ * @param {boolean} zuluAtZero - The "t" modifier: Z for UTC
28
+ * @returns {string}
29
+ */
30
+ function numericOffset(offset, token, zuluAtZero) {
31
+ if (zuluAtZero && offset === 0) return "Z";
32
+ const chars = Array.from(token);
33
+ const digitChars = chars.filter((c) => /\p{Nd}/u.test(c));
34
+ const zero = digitChars.length ? zeroOf(digitChars[0]) : 0x30;
35
+ const separator = chars.find((c) => !/[\p{Nd}#]/u.test(c));
36
+ const hours = Math.floor(Math.abs(offset) / 60);
37
+ const minutes = Math.abs(offset) % 60;
38
+ const pad = (n, width) => String(n).padStart(width, "0");
39
+ let text;
40
+ if (separator !== undefined) {
41
+ const hourDigits = chars.indexOf(separator);
42
+ text = pad(hours, hourDigits) + separator + pad(minutes, 2);
43
+ } else if (digitChars.length >= 3) {
44
+ text = pad(hours, digitChars.length - 2) + pad(minutes, 2);
45
+ } else {
46
+ text =
47
+ pad(hours, digitChars.length) + (minutes ? `:${pad(minutes, 2)}` : "");
48
+ }
49
+ const digits = text.replace(/[0-9]/g, (d) =>
50
+ String.fromCodePoint(zero + Number(d)),
51
+ );
52
+ return (offset < 0 ? "-" : "+") + digits;
53
+ }
54
+
55
+ /**
56
+ * The numeric format of an offset without presentation modifier: "01:01",
57
+ * or only the hours (minutes when not zero) when a maximum width below
58
+ * five leaves no room for "hh:mm", as XSLT 2.0 erratum E29 has it.
59
+ * @param {import("./datePicture.js").Marker} marker
60
+ * @returns {string}
61
+ */
62
+ function defaultToken(marker) {
63
+ if (marker.maxWidth === null || marker.maxWidth >= 5) {
64
+ return "01:01";
65
+ }
66
+ return "0".repeat(Math.min(2, Math.max(1, marker.minWidth ?? 1)));
67
+ }
68
+
69
+ /**
70
+ * Formats a timezone component.
71
+ * @param {number|null} timezone - Minutes, null when absent
72
+ * @param {import("./datePicture.js").Marker} marker - Component Z or z
73
+ * @param {string|null} [zone] - Name of the timezone, from the place
74
+ * @returns {string}
75
+ */
76
+ export function formatTimezone(timezone, marker, zone = null) {
77
+ const token = marker.first ?? defaultToken(marker);
78
+ if (timezone === null) return token === "Z" ? "J" : "";
79
+ const gmt = marker.component === "z" ? "GMT" : "";
80
+ if (token === "Z" && timezone % 60 === 0 && Math.abs(timezone) <= 720) {
81
+ return MILITARY[timezone / 60 + 12];
82
+ }
83
+ if (/^(N|n|Nn)$/.test(token) && (zone || NAMES.has(timezone))) {
84
+ return zone ?? NAMES.get(timezone);
85
+ }
86
+ const numeric = /^[\p{Nd}#]/u.test(token) ? token : "01:01";
87
+ const text = gmt + numericOffset(timezone, numeric, marker.second === "t");
88
+ return marker.minWidth === null ? text : text.padEnd(marker.minWidth, " ");
89
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Higher-order functions (F&O 3.1 sections 16 and 17.2): functions on
3
+ * function items, and the functions that take function items.
4
+ *
5
+ * @module @tradik/xslt3/functions/higherOrder
6
+ */
7
+
8
+ import { XPathError } from "../errors.js";
9
+ import { AtomicValue } from "../xdm/atomic.js";
10
+ import { types } from "../xdm/types.js";
11
+ import { integerItem } from "../xpath/eval/atomics.js";
12
+ import { sortFunctions } from "./sort.js";
13
+
14
+ /** Function definitions. */
15
+ export const higherOrderFunctions = [
16
+ {
17
+ local: "for-each",
18
+ params: ["item()*", "function(item()) as item()*"],
19
+ returns: "item()*",
20
+ impl: ([sequence, [f]]) => sequence.flatMap((item) => f.invoke([[item]])),
21
+ },
22
+ {
23
+ local: "filter",
24
+ params: ["item()*", "function(item()) as xs:boolean"],
25
+ returns: "item()*",
26
+ impl: ([sequence, [f]]) =>
27
+ sequence.filter((item) => f.invoke([[item]])[0].value),
28
+ },
29
+ {
30
+ local: "fold-left",
31
+ params: ["item()*", "item()*", "function(item()*, item()) as item()*"],
32
+ returns: "item()*",
33
+ impl: ([sequence, zero, [f]]) =>
34
+ sequence.reduce((acc, item) => f.invoke([acc, [item]]), zero),
35
+ },
36
+ {
37
+ local: "fold-right",
38
+ params: ["item()*", "item()*", "function(item(), item()*) as item()*"],
39
+ returns: "item()*",
40
+ impl: ([sequence, zero, [f]]) =>
41
+ sequence.reduceRight((acc, item) => f.invoke([[item], acc]), zero),
42
+ },
43
+ {
44
+ local: "for-each-pair",
45
+ params: ["item()*", "item()*", "function(item(), item()) as item()*"],
46
+ returns: "item()*",
47
+ impl: ([a, b, [f]]) => {
48
+ const result = [];
49
+ const length = Math.min(a.length, b.length);
50
+ for (let i = 0; i < length; i++) {
51
+ result.push(...f.invoke([[a[i]], [b[i]]]));
52
+ }
53
+ return result;
54
+ },
55
+ },
56
+ {
57
+ local: "apply",
58
+ params: ["function(*)", "array(*)"],
59
+ returns: "item()*",
60
+ impl: ([[f], [array]]) => {
61
+ if (f.arity !== array.size) {
62
+ throw new XPathError(
63
+ "FOAP0001",
64
+ `The function has arity ${f.arity}, the array ${array.size} members`,
65
+ );
66
+ }
67
+ return f.invoke(array.members);
68
+ },
69
+ },
70
+ {
71
+ local: "function-name",
72
+ params: ["function(*)"],
73
+ returns: "xs:QName?",
74
+ impl: ([[f]]) => (f.name ? [new AtomicValue(types.QName, f.name)] : []),
75
+ },
76
+ {
77
+ local: "function-arity",
78
+ params: ["function(*)"],
79
+ returns: "xs:integer",
80
+ impl: ([[f]]) => [integerItem(f.arity)],
81
+ },
82
+ {
83
+ local: "function-lookup",
84
+ params: ["xs:QName", "xs:integer"],
85
+ returns: "function(*)?",
86
+ focus: true,
87
+ impl: ([[name], [arity]], context) => {
88
+ const ctx = {
89
+ item: context.contextItem,
90
+ position: context.position,
91
+ size: context.size,
92
+ env: null,
93
+ dyn: context,
94
+ };
95
+ const { namespaceURI, localName } = name.value;
96
+ const f = context.lookupFunction(
97
+ namespaceURI,
98
+ localName,
99
+ Number(arity.value),
100
+ ctx,
101
+ );
102
+ return f ? [f] : [];
103
+ },
104
+ },
105
+ ...sortFunctions,
106
+ ];
@@ -0,0 +1,127 @@
1
+ /**
2
+ * fn:id, fn:element-with-id and fn:idref (F&O 3.1 sections 14.5.2 to
3
+ * 14.5.4). Without a schema, IDs are `xml:id` attributes and attributes
4
+ * declared ID in the internal DTD subset; IDREF/IDREFS attributes are
5
+ * those declared so in the internal DTD subset (see attributeTypes.js).
6
+ * Elements are never IDs themselves (that needs a schema), so
7
+ * fn:element-with-id finds the same elements as fn:id.
8
+ *
9
+ * @module @tradik/xslt3/functions/ids
10
+ */
11
+
12
+ import { XPathError } from "../errors.js";
13
+ import { namePatterns } from "../xdm/types.js";
14
+ import { attributesOf, rootOf } from "../xpath/eval/domNodes.js";
15
+ import { attributeType } from "./attributeTypes.js";
16
+ import { focusNode } from "./focus.js";
17
+ import { define } from "./support.js";
18
+
19
+ /** @param {string} s @returns {string[]} whitespace-separated tokens */
20
+ const tokens = (s) => s.split(/[\t\n\r ]+/).filter(Boolean);
21
+
22
+ /**
23
+ * The document node a search starts from.
24
+ * @param {Node} node
25
+ * @returns {Node}
26
+ * @throws {XPathError} FODC0001 when the tree is not rooted at a document
27
+ */
28
+ function documentOf(node) {
29
+ const root = rootOf(node);
30
+ if (root.nodeType !== 9 && root.nodeType !== 11) {
31
+ throw new XPathError("FODC0001", "The tree is not rooted at a document");
32
+ }
33
+ return root;
34
+ }
35
+
36
+ /**
37
+ * The elements of a tree in document order.
38
+ * @param {Node} root
39
+ * @returns {Generator<Element>}
40
+ */
41
+ function* elements(root) {
42
+ const stack = [root.firstChild];
43
+ while (stack.length) {
44
+ const node = stack.pop();
45
+ if (!node) continue;
46
+ stack.push(node.nextSibling);
47
+ if (node.nodeType === 1) {
48
+ yield node;
49
+ stack.push(node.firstChild);
50
+ }
51
+ }
52
+ }
53
+
54
+ /**
55
+ * fn:id: the elements with an ID attribute equal to one of the tokens,
56
+ * the first in document order for each value.
57
+ * @param {Array} args - [$arg, $node]
58
+ * @returns {Element[]}
59
+ */
60
+ function findIds([values, [node]]) {
61
+ const wanted = new Set(values.flatMap((v) => tokens(v.value)));
62
+ const result = [];
63
+ for (const element of elements(documentOf(node))) {
64
+ if (wanted.size === 0) break;
65
+ for (const attribute of attributesOf(element)) {
66
+ const value = tokens(attribute.value).join(" ");
67
+ if (wanted.has(value) && attributeType(attribute, element) === "ID") {
68
+ wanted.delete(value);
69
+ if (result.at(-1) !== element) result.push(element);
70
+ }
71
+ }
72
+ }
73
+ return result;
74
+ }
75
+
76
+ /**
77
+ * fn:idref: the IDREF and IDREFS attributes that refer to one of the IDs.
78
+ * @param {Array} args - [$arg, $node]
79
+ * @returns {Attr[]}
80
+ */
81
+ function findIdrefs([values, [node]]) {
82
+ const wanted = new Set(
83
+ values
84
+ .map((v) => tokens(v.value).join(" "))
85
+ .filter((v) => namePatterns.ncName.test(v)),
86
+ );
87
+ const result = [];
88
+ if (wanted.size === 0) return result;
89
+ for (const element of elements(documentOf(node))) {
90
+ for (const attribute of attributesOf(element)) {
91
+ const type = attributeType(attribute, element);
92
+ if (
93
+ (type === "IDREF" || type === "IDREFS") &&
94
+ tokens(attribute.value).some((t) => wanted.has(t))
95
+ ) {
96
+ result.push(attribute);
97
+ }
98
+ }
99
+ }
100
+ return result;
101
+ }
102
+
103
+ /**
104
+ * Definitions of a function in its two arities (the context node, else
105
+ * the given node, locates the document).
106
+ * @param {string} local
107
+ * @param {string} returns
108
+ * @param {(args: Array) => Node[]} impl
109
+ * @returns {object[]}
110
+ */
111
+ const family = (local, returns, impl) => [
112
+ define(
113
+ local,
114
+ ["xs:string*"],
115
+ returns,
116
+ ([values], context) => impl([values, [focusNode(context)]]),
117
+ { focus: true },
118
+ ),
119
+ define(local, ["xs:string*", "node()"], returns, impl),
120
+ ];
121
+
122
+ /** Function definitions. */
123
+ export const idFunctions = [
124
+ ...family("id", "element()*", findIds),
125
+ ...family("element-with-id", "element()*", findIds),
126
+ ...family("idref", "node()*", findIdrefs),
127
+ ];
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The JSON functions of F&O 3.1 section 17.5: fn:parse-json, fn:json-doc,
3
+ * fn:json-to-xml and fn:xml-to-json.
4
+ *
5
+ * @module @tradik/xslt3/functions/json
6
+ */
7
+
8
+ import { jsonToXmlFunctions } from "./jsonToXml.js";
9
+ import { parseJsonFunctions } from "./parseJson.js";
10
+ import { xmlToJsonFunctions } from "./xmlToJson.js";
11
+
12
+ /** Function definitions. */
13
+ export const jsonFunctions = [
14
+ ...parseJsonFunctions,
15
+ ...jsonToXmlFunctions,
16
+ ...xmlToJsonFunctions,
17
+ ];
@@ -0,0 +1,110 @@
1
+ /**
2
+ * The characters of JSON strings, with the escape sequences they were
3
+ * written with (fn:parse-json, fn:json-to-xml and the escaped strings of
4
+ * fn:xml-to-json).
5
+ *
6
+ * @module @tradik/xslt3/functions/json/jsonChars
7
+ */
8
+
9
+ import { XPathError } from "../../errors.js";
10
+
11
+ /**
12
+ * A character of a JSON string: its codepoint and, when it was written
13
+ * as an escape sequence, the sequence ("\\n", "\\uD834").
14
+ * @typedef {{cp: number, escape?: string}} JsonChar
15
+ */
16
+
17
+ const SIMPLE_ESCAPES = {
18
+ '"': 0x22,
19
+ "\\": 0x5c,
20
+ "/": 0x2f,
21
+ b: 0x08,
22
+ f: 0x0c,
23
+ n: 0x0a,
24
+ r: 0x0d,
25
+ t: 0x09,
26
+ };
27
+ const HEX4 = /[0-9A-Fa-f]{4}/y;
28
+
29
+ /**
30
+ * Reads the characters of a JSON string body.
31
+ * @param {string} text
32
+ * @param {number} start - Offset after the opening quote
33
+ * @param {(message: string) => never} fail - Raises the error
34
+ * @param {boolean} [quoted=true] - Ends at an unescaped quote, which must
35
+ * come, and rejects control characters; else reads to the end
36
+ * @returns {{chars: JsonChar[], end: number}} the characters and the
37
+ * offset of the closing quote (of the end)
38
+ */
39
+ export function scanChars(text, start, fail, quoted = true) {
40
+ const chars = [];
41
+ let pos = start;
42
+ for (;;) {
43
+ const cp = text.codePointAt(pos);
44
+ if (cp === undefined) {
45
+ if (quoted) fail("unterminated string");
46
+ break;
47
+ }
48
+ if (quoted && cp === 0x22) break;
49
+ if (quoted && cp < 0x20) fail("control character in a string");
50
+ if (cp !== 0x5c) {
51
+ chars.push({ cp });
52
+ pos += cp > 0xffff ? 2 : 1;
53
+ continue;
54
+ }
55
+ const char = text[pos + 1];
56
+ if (char === "u") {
57
+ HEX4.lastIndex = pos + 2;
58
+ if (!HEX4.test(text)) fail("invalid \\u escape");
59
+ const escape = text.slice(pos, pos + 6);
60
+ chars.push({ cp: parseInt(escape.slice(2), 16), escape });
61
+ pos += 6;
62
+ } else if (char !== undefined && Object.hasOwn(SIMPLE_ESCAPES, char)) {
63
+ chars.push({ cp: SIMPLE_ESCAPES[char], escape: `\\${char}` });
64
+ pos += 2;
65
+ } else {
66
+ fail(`invalid escape \\${char ?? ""}`);
67
+ }
68
+ }
69
+ return { chars: pairSurrogates(chars), end: pos };
70
+ }
71
+
72
+ /**
73
+ * The characters of a string that holds JSON escape sequences (the
74
+ * content of an fn:xml-to-json string or key marked as escaped).
75
+ * @param {string} text
76
+ * @returns {JsonChar[]}
77
+ * @throws {XPathError} FOJS0007 for an invalid escape sequence
78
+ */
79
+ export function unescapeJsonChars(text) {
80
+ const fail = (message) => {
81
+ throw new XPathError("FOJS0007", `Invalid JSON escape: ${message}`);
82
+ };
83
+ return scanChars(text, 0, fail, false).chars;
84
+ }
85
+
86
+ /**
87
+ * Joins escaped surrogate pairs (\uD834\uDD1E) into one character.
88
+ * @param {JsonChar[]} chars
89
+ * @returns {JsonChar[]}
90
+ */
91
+ function pairSurrogates(chars) {
92
+ const result = [];
93
+ for (let i = 0; i < chars.length; i++) {
94
+ const high = chars[i];
95
+ const low = chars[i + 1];
96
+ if (
97
+ high.cp >= 0xd800 &&
98
+ high.cp <= 0xdbff &&
99
+ low?.cp >= 0xdc00 &&
100
+ low.cp <= 0xdfff
101
+ ) {
102
+ const cp = (high.cp - 0xd800) * 0x400 + (low.cp - 0xdc00) + 0x10000;
103
+ result.push({ cp, escape: `${high.escape ?? ""}${low.escape ?? ""}` });
104
+ i++;
105
+ } else {
106
+ result.push(high);
107
+ }
108
+ }
109
+ return result;
110
+ }