@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,149 @@
1
+ /**
2
+ * Hooks of the dynamic context that reach outside the expression: text
3
+ * resources (fn:unparsed-text, fn:json-doc), XML parsing (fn:parse-xml)
4
+ * and collections (fn:collection, fn:uri-collection). Each is built from
5
+ * a dynamic option, with a default:
6
+ *
7
+ * - `textLoader(uri)`: returns the resource as a string, bytes, or
8
+ * `{content, encoding, mediaType}`; default: no resources, so an
9
+ * expression cannot read files unless the host allows it (pass
10
+ * `readFileUri` to read `file:` URIs in Node.js);
11
+ * - `xmlParser(text, baseUri)`: returns a Document, throws when the text
12
+ * is not well-formed; default: `globalThis.DOMParser` when present;
13
+ * - `collections(uri)`: returns the items of a collection (JavaScript
14
+ * values are converted as variables are), `uri` null for the default
15
+ * collection; default: no collections.
16
+ *
17
+ * @module @tradik/xslt3/xpath/eval/resources
18
+ */
19
+
20
+ import { XPathError } from "../../errors.js";
21
+ import { decodeText } from "../../functions/textDecoding.js";
22
+ import { toSequence } from "./values.js";
23
+
24
+ /**
25
+ * Reads `file:` URIs with node:fs (Node.js 22.3 and later). Not used by
26
+ * default: pass it as the `textLoader` option to let expressions read
27
+ * local files, only when the expressions are trusted.
28
+ * @param {string} uri - Absolute URI
29
+ * @returns {Uint8Array|null} the bytes, null for other schemes or
30
+ * without node:fs
31
+ */
32
+ export function readFileUri(uri) {
33
+ const fs = globalThis.process?.getBuiltinModule?.("node:fs");
34
+ if (!fs || !uri.startsWith("file:")) return null;
35
+ return fs.readFileSync(new globalThis.URL(uri));
36
+ }
37
+
38
+ /**
39
+ * The default text loader: no resource is available.
40
+ * @returns {null}
41
+ */
42
+ function noResources() {
43
+ return null;
44
+ }
45
+
46
+ /**
47
+ * The absolute URI of a resource reference.
48
+ * @param {string} href
49
+ * @param {string|undefined} baseUri - Static base URI
50
+ * @returns {string}
51
+ * @throws {XPathError} FOUT1170 for a fragment identifier, an invalid URI
52
+ * or a relative URI without base
53
+ */
54
+ export function resourceUri(href, baseUri) {
55
+ if (href.includes("#") || /%(?![0-9A-Fa-f]{2})/.test(href)) {
56
+ throw new XPathError("FOUT1170", `Invalid resource URI ${href}`);
57
+ }
58
+ try {
59
+ return new globalThis.URL(href, baseUri).href;
60
+ } catch (error) {
61
+ throw new XPathError("FOUT1170", `Cannot resolve the URI ${href}`, {
62
+ cause: error,
63
+ });
64
+ }
65
+ }
66
+
67
+ /**
68
+ * The text loading hook: resolves, loads (once per URI and encoding
69
+ * during an evaluation) and decodes.
70
+ * @param {((uri: string) => import("../../functions/textDecoding.js").TextResource)|undefined} loader
71
+ * @param {string|undefined} baseUri
72
+ * @returns {(href: string, encoding?: string, base?: string) => string}
73
+ * the hook; `base` replaces the base URI of relative references
74
+ */
75
+ export function createTextLoader(loader = noResources, baseUri) {
76
+ const texts = new Map();
77
+ return (href, encoding, base = baseUri) => {
78
+ const uri = resourceUri(href, base);
79
+ const key = `${encoding ?? ""} ${uri}`;
80
+ if (texts.has(key)) return texts.get(key);
81
+ let resource;
82
+ try {
83
+ resource = loader(uri);
84
+ } catch (error) {
85
+ throw new XPathError("FOUT1170", `Cannot read ${uri}`, { cause: error });
86
+ }
87
+ if (resource === null || resource === undefined) {
88
+ throw new XPathError("FOUT1170", `No resource at ${uri}`);
89
+ }
90
+ const text = decodeText(resource, encoding);
91
+ texts.set(key, text);
92
+ return text;
93
+ };
94
+ }
95
+
96
+ /**
97
+ * The XML parsing hook.
98
+ * @param {((text: string, baseUri?: string) => Document)|undefined} parser
99
+ * @returns {(text: string, baseUri?: string) => Document}
100
+ */
101
+ export function createXmlParser(parser) {
102
+ if (parser) return parser;
103
+ return (text) => {
104
+ const DOMParser = globalThis.DOMParser;
105
+ if (!DOMParser) {
106
+ throw new XPathError(
107
+ "FODC0006",
108
+ "No XML parser: pass the xmlParser option",
109
+ );
110
+ }
111
+ const document = new DOMParser().parseFromString(text, "application/xml");
112
+ if (document.getElementsByTagName("parsererror").length) {
113
+ throw new XPathError("FODC0006", "The string is not well-formed XML");
114
+ }
115
+ return document;
116
+ };
117
+ }
118
+
119
+ /**
120
+ * The collection hook.
121
+ * @param {((uri: string|null) => Array|null|undefined)|undefined} collections
122
+ * @param {string|undefined} baseUri
123
+ * @returns {(href: string|null, base?: string) => Array} the hook; `base`
124
+ * replaces the base URI of relative references
125
+ * @throws {XPathError} FODC0002 for an unknown collection, FODC0004 for an
126
+ * invalid URI
127
+ */
128
+ export function createCollections(collections, baseUri) {
129
+ return (href, base = baseUri) => {
130
+ let uri = null;
131
+ if (href !== null) {
132
+ try {
133
+ uri = new globalThis.URL(href, base).href;
134
+ } catch (error) {
135
+ throw new XPathError("FODC0004", `Invalid collection URI ${href}`, {
136
+ cause: error,
137
+ });
138
+ }
139
+ }
140
+ const items = collections?.(uri);
141
+ if (!items) {
142
+ throw new XPathError(
143
+ "FODC0002",
144
+ uri === null ? "No default collection" : `No collection ${uri}`,
145
+ );
146
+ }
147
+ return toSequence(items);
148
+ };
149
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Variables and focus at compile time and at run time.
3
+ *
4
+ * Compile time: a scope is the static context plus the in-scope variables,
5
+ * a linked list from the innermost binding outwards. A variable reference
6
+ * is resolved once, to its depth in that list.
7
+ *
8
+ * Run time: an evaluator receives a context `{item, position, size, env,
9
+ * dyn}`: the focus (item undefined when absent), the environment (a linked
10
+ * list of values matching the compile-time scope, so a reference walks
11
+ * `depth` links) and the dynamic context shared by the whole evaluation.
12
+ *
13
+ * @module @tradik/xslt3/xpath/eval/scope
14
+ */
15
+
16
+ import { XPathError } from "../../errors.js";
17
+
18
+ /**
19
+ * @typedef {object} Scope
20
+ * @property {import("./staticContext.js").StaticContext} sc
21
+ * @property {{key: string, next: object}|null} vars - Innermost binding first
22
+ */
23
+
24
+ /**
25
+ * @typedef {object} Context
26
+ * @property {*} item - Context item, undefined when the focus is absent
27
+ * @property {number} position - Context position
28
+ * @property {number} size - Context size
29
+ * @property {{value: Array, next: object}|null} env - Variable values
30
+ * @property {object} dyn - Dynamic context (see dynamicContext.js)
31
+ */
32
+
33
+ /**
34
+ * @typedef {(ctx: Context) => Array} Evaluator - A compiled expression
35
+ */
36
+
37
+ /**
38
+ * @param {import("./staticContext.js").StaticContext} sc
39
+ * @param {string[]} keys - Clark names of the external variables, the
40
+ * first one outermost
41
+ * @returns {Scope}
42
+ */
43
+ export function rootScope(sc, keys) {
44
+ let scope = { sc, vars: null };
45
+ for (const key of keys) scope = bindVariable(scope, key);
46
+ return scope;
47
+ }
48
+
49
+ /**
50
+ * @param {Scope} scope
51
+ * @param {string} key - Clark name of the new variable
52
+ * @returns {Scope} the scope with the variable added innermost
53
+ */
54
+ export function bindVariable(scope, key) {
55
+ return { sc: scope.sc, vars: { key, next: scope.vars } };
56
+ }
57
+
58
+ /**
59
+ * @param {Scope} scope
60
+ * @param {string} key - Clark name
61
+ * @returns {number} depth of the innermost binding of the name, -1 when
62
+ * the variable is not in scope
63
+ */
64
+ export function variableDepth(scope, key) {
65
+ let depth = 0;
66
+ for (let v = scope.vars; v !== null; v = v.next, depth++) {
67
+ if (v.key === key) return depth;
68
+ }
69
+ return -1;
70
+ }
71
+
72
+ /**
73
+ * @param {Context} ctx
74
+ * @param {Array} value
75
+ * @returns {Context} the context with a value bound innermost
76
+ */
77
+ export function withVariable(ctx, value) {
78
+ return {
79
+ item: ctx.item,
80
+ position: ctx.position,
81
+ size: ctx.size,
82
+ env: { value, next: ctx.env },
83
+ dyn: ctx.dyn,
84
+ };
85
+ }
86
+
87
+ /**
88
+ * @param {Context} ctx
89
+ * @param {*} item
90
+ * @param {number} position
91
+ * @param {number} size
92
+ * @returns {Context} the context with a new focus
93
+ */
94
+ export function withFocus(ctx, item, position, size) {
95
+ return { item, position, size, env: ctx.env, dyn: ctx.dyn };
96
+ }
97
+
98
+ /**
99
+ * @param {Context} ctx
100
+ * @returns {*} the context item
101
+ * @throws {XPathError} XPDY0002 when the focus is absent
102
+ */
103
+ export function contextItem(ctx) {
104
+ if (ctx.item === undefined) {
105
+ throw new XPathError("XPDY0002", "The context item is absent");
106
+ }
107
+ return ctx.item;
108
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Sequence types (XPath 3.1 section 2.5) compiled from the AST, and
3
+ * sequence type matching (section 2.5.5): `instance of`, `treat as`,
4
+ * typed variables and function signatures.
5
+ *
6
+ * A compiled sequence type is `{itemType, occurrence}` with `itemType`
7
+ * null for empty-sequence(). A compiled item type has a `kind` ("item",
8
+ * "atomic", "node", "anyFunction", "function", "anyMap", "map",
9
+ * "anyArray", "array"), the fields of that kind and `matches(item)`.
10
+ *
11
+ * @module @tradik/xslt3/xpath/eval/sequenceType
12
+ */
13
+
14
+ import {
15
+ isArray,
16
+ isAtomic,
17
+ isFunctionItem,
18
+ isMap,
19
+ isNode,
20
+ } from "../../xdm/atomic.js";
21
+ import { derivesFrom, types } from "../../xdm/types.js";
22
+ import { compileKindTest } from "./nodeTests.js";
23
+ import { isSubtype } from "./subtype.js";
24
+ import { resolveAtomicType } from "./typeNames.js";
25
+
26
+ /** @typedef {{itemType: object|null, occurrence: string}} SequenceType */
27
+
28
+ /** item()* */
29
+ export const ANY_SEQUENCE = Object.freeze({
30
+ itemType: Object.freeze({ kind: "item", matches: () => true }),
31
+ occurrence: "*",
32
+ });
33
+
34
+ /**
35
+ * Atomic item type of a list of member types.
36
+ * @param {object[]} members - Atomic type descriptors
37
+ * @returns {object} the item type
38
+ */
39
+ export function atomicItemType(members) {
40
+ const matches =
41
+ members.length === 1
42
+ ? (item) => isAtomic(item) && derivesFrom(item.type, members[0])
43
+ : (item) =>
44
+ isAtomic(item) && members.some((t) => derivesFrom(item.type, t));
45
+ return { kind: "atomic", types: members, matches };
46
+ }
47
+
48
+ /** Signature of every map as a function: function(xs:anyAtomicType) as item()* */
49
+ export const MAP_SIGNATURE = Object.freeze({
50
+ params: [{ itemType: atomicItemType([types.anyAtomicType]), occurrence: "" }],
51
+ returns: ANY_SEQUENCE,
52
+ });
53
+
54
+ /** Signature of every array as a function: function(xs:integer) as item()* */
55
+ export const ARRAY_SIGNATURE = Object.freeze({
56
+ params: [{ itemType: atomicItemType([types.integer]), occurrence: "" }],
57
+ returns: ANY_SEQUENCE,
58
+ });
59
+
60
+ /**
61
+ * @param {number} length - Number of items
62
+ * @param {string} occurrence - "", "?", "*" or "+"
63
+ * @returns {boolean} whether the number of items is allowed
64
+ */
65
+ export function occurrenceAllows(length, occurrence) {
66
+ if (length === 1) return true;
67
+ if (length === 0) return occurrence === "?" || occurrence === "*";
68
+ return occurrence === "*" || occurrence === "+";
69
+ }
70
+
71
+ /**
72
+ * @param {Array} sequence
73
+ * @param {SequenceType} type
74
+ * @returns {boolean} whether the sequence matches the type
75
+ */
76
+ export function matchesSequenceType(sequence, type) {
77
+ if (type.itemType === null) return sequence.length === 0;
78
+ if (!occurrenceAllows(sequence.length, type.occurrence)) return false;
79
+ const { matches } = type.itemType;
80
+ for (const item of sequence) if (!matches(item)) return false;
81
+ return true;
82
+ }
83
+
84
+ /**
85
+ * @param {*} item - A function item, map or array
86
+ * @returns {{params: SequenceType[], returns: SequenceType}} its signature
87
+ */
88
+ export function signatureOf(item) {
89
+ if (isMap(item)) return MAP_SIGNATURE;
90
+ if (isArray(item)) return ARRAY_SIGNATURE;
91
+ return item.signature;
92
+ }
93
+
94
+ /**
95
+ * Whether a map or an array matches a typed function test (XPath 3.1
96
+ * 2.5.5.7): by its actual entries, as a function of one key or position.
97
+ * @param {*} item - Map or array
98
+ * @param {object} test - Compiled TypedFunctionTest
99
+ * @returns {boolean}
100
+ */
101
+ function lookupMatches(item, test) {
102
+ if (test.params.length !== 1) return false;
103
+ const [param] = test.params;
104
+ const { returns } = test;
105
+ if (isMap(item)) {
106
+ return (
107
+ isSubtype(param, MAP_SIGNATURE.params[0]) &&
108
+ matchesSequenceType([], returns) &&
109
+ [...item.entries.values()].every(({ value }) =>
110
+ matchesSequenceType(value, returns),
111
+ )
112
+ );
113
+ }
114
+ return (
115
+ isSubtype(param, ARRAY_SIGNATURE.params[0]) &&
116
+ item.members.every((member) => matchesSequenceType(member, returns))
117
+ );
118
+ }
119
+
120
+ /** Item type compilers by AST node type. */
121
+ const itemTypes = {
122
+ AnyItemTest: () => ANY_SEQUENCE.itemType,
123
+ AtomicType: (node, sc) => atomicItemType(resolveAtomicType(node.name, sc)),
124
+ AnyFunctionTest: () => ({ kind: "anyFunction", matches: isFunctionItem }),
125
+ AnyMapTest: () => ({ kind: "anyMap", matches: isMap }),
126
+ AnyArrayTest: () => ({ kind: "anyArray", matches: isArray }),
127
+ TypedFunctionTest(node, sc) {
128
+ const type = {
129
+ kind: "function",
130
+ params: node.paramTypes.map((p) => compileSequenceType(p, sc)),
131
+ returns: compileSequenceType(node.returnType, sc),
132
+ };
133
+ type.matches = (item) => {
134
+ if (!isFunctionItem(item)) return false;
135
+ if (isMap(item) || isArray(item)) return lookupMatches(item, type);
136
+ const signature = signatureOf(item);
137
+ if (signature.params.length !== type.params.length) return false;
138
+ return isSubtype(
139
+ { itemType: { kind: "function", ...signature }, occurrence: "" },
140
+ { itemType: type, occurrence: "" },
141
+ );
142
+ };
143
+ return type;
144
+ },
145
+ TypedMapTest(node, sc) {
146
+ const keyType = compileItemType(node.keyType, sc);
147
+ const valueType = compileSequenceType(node.valueType, sc);
148
+ const matches = (item) =>
149
+ isMap(item) &&
150
+ [...item.entries.values()].every(
151
+ ({ key, value }) =>
152
+ keyType.matches(key) && matchesSequenceType(value, valueType),
153
+ );
154
+ return { kind: "map", keyType, valueType, matches };
155
+ },
156
+ TypedArrayTest(node, sc) {
157
+ const memberType = compileSequenceType(node.memberType, sc);
158
+ const matches = (item) =>
159
+ isArray(item) &&
160
+ item.members.every((member) => matchesSequenceType(member, memberType));
161
+ return { kind: "array", memberType, matches };
162
+ },
163
+ };
164
+
165
+ /**
166
+ * @param {import("../syntax/typeAst.js").ItemType} node
167
+ * @param {import("./staticContext.js").StaticContext} sc
168
+ * @returns {object} the compiled item type
169
+ * @throws {XPathError} XPST0051 (unknown atomic type), XPST0008, XPST0081
170
+ */
171
+ export function compileItemType(node, sc) {
172
+ const compiler = itemTypes[node.type];
173
+ if (compiler) return compiler(node, sc);
174
+ const test = compileKindTest(node, sc);
175
+ return {
176
+ kind: "node",
177
+ test,
178
+ matches: (item) => isNode(item) && test.matches(item),
179
+ };
180
+ }
181
+
182
+ /**
183
+ * @param {import("../syntax/typeAst.js").SequenceType} node
184
+ * @param {import("./staticContext.js").StaticContext} sc
185
+ * @returns {SequenceType}
186
+ */
187
+ export function compileSequenceType(node, sc) {
188
+ return {
189
+ itemType:
190
+ node.itemType === null ? null : compileItemType(node.itemType, sc),
191
+ occurrence: node.occurrence,
192
+ };
193
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * The static context of an XPath expression (XPath 3.1 section 2.1.1) built
3
+ * from the options of compileXPath, and the resolution of the names written
4
+ * in an expression to expanded names.
5
+ *
6
+ * @module @tradik/xslt3/xpath/eval/staticContext
7
+ */
8
+
9
+ import { XPathError } from "../../errors.js";
10
+ import { NS } from "../../functions/signatures.js";
11
+ import { XS_NAMESPACE } from "../../xdm/types.js";
12
+ import { createDecimalFormats } from "./decimalFormats.js";
13
+
14
+ /** URI of the Unicode codepoint collation, the only one supported. */
15
+ export const CODEPOINT_COLLATION =
16
+ "http://www.w3.org/2005/xpath-functions/collation/codepoint";
17
+
18
+ /** Namespace of the error codes. */
19
+ export const ERR_NAMESPACE = "http://www.w3.org/2005/xqt-errors";
20
+
21
+ /** Namespaces predeclared in every static context. */
22
+ export const PREDECLARED_NAMESPACES = Object.freeze({
23
+ xml: "http://www.w3.org/XML/1998/namespace",
24
+ xs: XS_NAMESPACE,
25
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
26
+ fn: NS.fn,
27
+ math: NS.math,
28
+ map: NS.map,
29
+ array: NS.array,
30
+ err: ERR_NAMESPACE,
31
+ });
32
+
33
+ /**
34
+ * @typedef {object} StaticContext
35
+ * @property {Map<string, string>} namespaces - Prefix to URI
36
+ * @property {string} defaultElementNamespace - "" for none
37
+ * @property {string} defaultFunctionNamespace
38
+ * @property {object} functions - Function library (functions/registry.js)
39
+ * @property {string|undefined} baseUri - Static base URI
40
+ * @property {string} defaultCollation - Collation URI
41
+ * @property {boolean} backwardsCompatible - XPath 1.0 compatibility mode
42
+ * @property {{get: (name: string) => object|undefined}} decimalFormats -
43
+ * Decimal formats by name (see decimalFormats.js)
44
+ */
45
+
46
+ /**
47
+ * Normalizes namespace bindings given as an object, a Map or an array of
48
+ * `{prefix, uri}`.
49
+ * @param {object|Map|Array|undefined} namespaces
50
+ * @returns {Array<[string, string]>}
51
+ */
52
+ function namespacePairs(namespaces) {
53
+ if (!namespaces) return [];
54
+ if (Array.isArray(namespaces)) {
55
+ return namespaces.map(({ prefix, uri }) => [prefix ?? "", uri]);
56
+ }
57
+ if (namespaces instanceof Map) return [...namespaces];
58
+ return Object.entries(namespaces);
59
+ }
60
+
61
+ /**
62
+ * Builds a static context.
63
+ * @param {object} options - See compileXPath in xpath/index.js
64
+ * @param {object} functions - Function library
65
+ * @returns {StaticContext}
66
+ * @throws {XPathError} FOCH0002 for a default collation other than the
67
+ * codepoint collation
68
+ */
69
+ export function createStaticContext(options, functions) {
70
+ const namespaces = new Map(Object.entries(PREDECLARED_NAMESPACES));
71
+ let defaultElementNamespace = options.defaultElementNamespace ?? "";
72
+ for (const [prefix, uri] of namespacePairs(options.namespaces)) {
73
+ if (prefix === "") defaultElementNamespace = uri;
74
+ else namespaces.set(prefix, uri);
75
+ }
76
+ const defaultCollation = options.defaultCollation ?? CODEPOINT_COLLATION;
77
+ if (defaultCollation !== CODEPOINT_COLLATION) {
78
+ throw new XPathError(
79
+ "FOCH0002",
80
+ `Unsupported collation ${defaultCollation}`,
81
+ );
82
+ }
83
+ const sc = {
84
+ namespaces,
85
+ defaultElementNamespace,
86
+ defaultFunctionNamespace: options.defaultFunctionNamespace ?? NS.fn,
87
+ functions,
88
+ baseUri: options.baseUri,
89
+ defaultCollation,
90
+ backwardsCompatible: Boolean(options.backwardsCompatible),
91
+ };
92
+ sc.decimalFormats = createDecimalFormats(options.decimalFormats, (name) =>
93
+ variableKey(name, sc),
94
+ );
95
+ return sc;
96
+ }
97
+
98
+ /**
99
+ * Resolves a name as written to its namespace URI.
100
+ * @param {import("../syntax/ast.js").QName} name
101
+ * @param {StaticContext} sc
102
+ * @param {string} defaultNamespace - URI for an unprefixed name
103
+ * @returns {string} the namespace URI ("" for none)
104
+ * @throws {XPathError} XPST0081 for an undeclared prefix
105
+ */
106
+ export function namespaceOf(name, sc, defaultNamespace) {
107
+ if (name.uri !== null) return name.uri;
108
+ if (name.prefix === null) return defaultNamespace;
109
+ const uri = sc.namespaces.get(name.prefix);
110
+ if (uri === undefined) {
111
+ throw new XPathError(
112
+ "XPST0081",
113
+ `The prefix ${name.prefix} is not declared`,
114
+ );
115
+ }
116
+ return uri;
117
+ }
118
+
119
+ /**
120
+ * Expanded name in Clark notation, `{uri}local`.
121
+ * @param {string} uri
122
+ * @param {string} local
123
+ * @returns {string}
124
+ */
125
+ export const clark = (uri, local) => `{${uri}}${local}`;
126
+
127
+ /**
128
+ * Parses a variable name given in the options: `Q{uri}local`,
129
+ * `prefix:local` (resolved with the static namespaces) or `local`.
130
+ * @param {string} text
131
+ * @param {StaticContext} sc
132
+ * @returns {string} Clark name
133
+ * @throws {XPathError} XPST0081 for an undeclared prefix
134
+ */
135
+ export function variableKey(text, sc) {
136
+ const eqName = /^Q\{([^}]*)\}(.+)$/.exec(text);
137
+ if (eqName) return clark(eqName[1], eqName[2]);
138
+ const colon = text.indexOf(":");
139
+ if (colon < 0) return clark("", text);
140
+ const name = { prefix: text.slice(0, colon), local: text.slice(colon + 1) };
141
+ return clark(namespaceOf({ ...name, uri: null }, sc, ""), name.local);
142
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The `/` operator: a step applied to each node of the left operand, and
3
+ * the results combined (XPath 3.1 section 3.3.1.1): nodes in document
4
+ * order without duplicates, or atomic values and functions in order.
5
+ *
6
+ * Sorting is skipped where the order is known: a single input node and an
7
+ * axis step, or sorted sibling inputs and an axis that stays inside each
8
+ * input's subtree (child, attribute, namespace, self, descendant,
9
+ * descendant-or-self), whose results are then disjoint and in order.
10
+ *
11
+ * @module @tradik/xslt3/xpath/eval/steps
12
+ */
13
+
14
+ import { XPathError } from "../../errors.js";
15
+ import { isNode } from "../../xdm/atomic.js";
16
+ import { isContainer, parentOf } from "./domNodes.js";
17
+ import { withFocus } from "./scope.js";
18
+
19
+ const EMPTY = Object.freeze([]);
20
+
21
+ /** Axes that select inside the subtree of the context node. */
22
+ const SUBTREE_AXES = new Set([
23
+ "child",
24
+ "attribute",
25
+ "namespace",
26
+ "self",
27
+ "descendant",
28
+ "descendant-or-self",
29
+ ]);
30
+
31
+ /** Axes that select only children or descendants of the context node. */
32
+ const CHILD_AXES = new Set(["child", "descendant"]);
33
+
34
+ /**
35
+ * Combines the results of a step applied to each input item.
36
+ * @param {Array[]} results - One sequence per input item
37
+ * @param {import("./scope.js").Context} ctx
38
+ * @returns {Array} nodes in document order, or the concatenated values
39
+ * @throws {XPathError} XPTY0018 when nodes and other items are mixed
40
+ */
41
+ function combine(results, ctx) {
42
+ const all = results.flat();
43
+ let nodes = 0;
44
+ for (const item of all) if (isNode(item)) nodes++;
45
+ if (nodes === 0) return all;
46
+ if (nodes !== all.length) {
47
+ throw new XPathError(
48
+ "XPTY0018",
49
+ "A path step returned both nodes and other items",
50
+ );
51
+ }
52
+ return ctx.dyn.order.sort(all);
53
+ }
54
+
55
+ /**
56
+ * @param {Node[]} nodes - At least two nodes
57
+ * @returns {boolean} whether all the nodes have the same parent
58
+ */
59
+ function areSiblings(nodes) {
60
+ const parent = parentOf(nodes[0]);
61
+ if (parent === null) return false;
62
+ for (let i = 1; i < nodes.length; i++) {
63
+ if (parentOf(nodes[i]) !== parent) return false;
64
+ }
65
+ return true;
66
+ }
67
+
68
+ /**
69
+ * Evaluator of `E1/E2` for an evaluated E1.
70
+ * @param {import("./scope.js").Evaluator} step - E2
71
+ * @param {string|null} axis - Axis of E2 when it is an axis step
72
+ * @returns {(input: Array, ctx: object, sorted: boolean) => Array} the
73
+ * combined result; `sorted` tells that the input is in document order
74
+ */
75
+ export function stepper(step, axis) {
76
+ const subtree = SUBTREE_AXES.has(axis);
77
+ const childless = CHILD_AXES.has(axis);
78
+ return (input, ctx, sorted) => {
79
+ const size = input.length;
80
+ if (size === 0) return EMPTY;
81
+ const results = [];
82
+ for (let i = 0; i < size; i++) {
83
+ if (!isNode(input[i])) {
84
+ throw new XPathError(
85
+ "XPTY0019",
86
+ "The left operand of / must contain only nodes",
87
+ );
88
+ }
89
+ // An axis step finds no children nor descendants of a leaf node (a
90
+ // shortcut for the text nodes reached by `//`)
91
+ if (childless && !isContainer(input[i])) continue;
92
+ results.push(step(withFocus(ctx, input[i], i + 1, size)));
93
+ }
94
+ if (size === 1 && axis !== null) return results[0] ?? EMPTY;
95
+ if (sorted && subtree && areSiblings(input)) return results.flat();
96
+ return combine(results, ctx);
97
+ };
98
+ }