@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,146 @@
1
+ /**
2
+ * xsl:accumulator declarations (XSLT 3.0 section 18.2.1): the initial
3
+ * value, the rules (pattern, phase, new value computed with `$value`
4
+ * bound to the old one) and the declared type; and the use-accumulators
5
+ * attributes of xsl:mode, xsl:global-context-item, xsl:source-document
6
+ * and xsl:merge-source (section 18.2.2).
7
+ *
8
+ * @module @tradik/xslt3/xslt/compiler/accumulatorDecl
9
+ */
10
+
11
+ import { typeConverter } from "../instructions/variables.js";
12
+ import { attr, clarkOf, isXsl, xsltError } from "../names.js";
13
+ import { compilePattern } from "../patterns/compile.js";
14
+ import { evaluate } from "../runtime/context.js";
15
+ import { bodySequence } from "../runtime/values.js";
16
+ import { checkAttributes, required, yesNo } from "./attributes.js";
17
+ import { compileBody } from "./body.js";
18
+ import { declareModeAccumulators, useAccumulators } from "./useAccumulators.js";
19
+
20
+ /** Clark name of the variable of the old value in accumulator rules. */
21
+ export const VALUE_KEY = "{}value";
22
+
23
+ /**
24
+ * Compiles a rule of an accumulator.
25
+ * @param {Element} element - xsl:accumulator-rule
26
+ * @param {object} cx
27
+ * @param {(value: Array) => Array} convert
28
+ * @returns {object} `{pattern, end, value(xc, machine)}`
29
+ */
30
+ function compileRule(element, cx, convert) {
31
+ checkAttributes(element);
32
+ const phase = (attr(element, "phase") ?? "start").trim();
33
+ if (phase !== "start" && phase !== "end") {
34
+ throw xsltError("XTSE0020", `Invalid phase "${phase}"`);
35
+ }
36
+ const global = cx.globalScope();
37
+ const scope = { ...global, vars: { key: VALUE_KEY, next: global.vars } };
38
+ const select = attr(element, "select");
39
+ const children = cx.children(element);
40
+ if (select !== undefined && children.length > 0) {
41
+ throw xsltError("XTSE0010", "xsl:accumulator-rule has select and content");
42
+ }
43
+ let value;
44
+ if (select !== undefined) {
45
+ const expr = cx.exprs.xpath(select, element, scope.vars);
46
+ value = (xc) => convert(evaluate(expr, xc));
47
+ } else {
48
+ const body = compileBody(element, cx, scope, children);
49
+ value = (xc, machine) => convert(bodySequence(body, xc, machine));
50
+ }
51
+ return {
52
+ pattern: compilePattern(required(element, "match"), element, cx),
53
+ end: phase === "end",
54
+ value,
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Compiles an xsl:accumulator.
60
+ * @param {Element} element
61
+ * @param {object} cx
62
+ * @param {string} key - Its Clark name
63
+ * @returns {object} the accumulator
64
+ */
65
+ function compileAccumulator(element, cx, key) {
66
+ yesNo(element, "streamable", false);
67
+ const asText = attr(element, "as");
68
+ const type =
69
+ asText === undefined ? null : cx.exprs.sequenceType(asText, element);
70
+ const convert = typeConverter(type, "XPTY0004", `accumulator ${key}`);
71
+ const initial = cx.exprs.xpath(
72
+ required(element, "initial-value"),
73
+ element,
74
+ cx.globalScope().vars,
75
+ );
76
+ const children = cx.children(element).filter((c) => !isXsl(c, "fallback"));
77
+ if (
78
+ children.length === 0 ||
79
+ !children.every((child) => isXsl(child, "accumulator-rule"))
80
+ ) {
81
+ throw xsltError(
82
+ "XTSE0010",
83
+ "xsl:accumulator contains xsl:accumulator-rule elements",
84
+ );
85
+ }
86
+ return {
87
+ key,
88
+ initial: (xc) => convert(evaluate(initial, xc)),
89
+ rules: children.map((child) => compileRule(child, cx, convert)),
90
+ };
91
+ }
92
+
93
+ /**
94
+ * The declaration of each name with the highest import precedence.
95
+ * @param {object[]} declarations - `{key, element, precedence}`
96
+ * @returns {Map<string, object>}
97
+ * @throws {import("../../errors.js").XPathError} XTSE3350 for two of
98
+ * them at that precedence
99
+ */
100
+ function highestPrecedence(declarations) {
101
+ const chosen = new Map();
102
+ const duplicated = new Set();
103
+ for (const declaration of declarations) {
104
+ const current = chosen.get(declaration.key);
105
+ if (current && current.precedence === declaration.precedence) {
106
+ duplicated.add(declaration.key);
107
+ } else if (!current || current.precedence < declaration.precedence) {
108
+ chosen.set(declaration.key, declaration);
109
+ duplicated.delete(declaration.key);
110
+ }
111
+ }
112
+ const [duplicate] = duplicated;
113
+ if (duplicate !== undefined) {
114
+ throw xsltError("XTSE3350", `Two accumulators are named ${duplicate}`);
115
+ }
116
+ return chosen;
117
+ }
118
+
119
+ /**
120
+ * Compiles the accumulators of a stylesheet into `cx.accumulators`, and
121
+ * the use-accumulators attributes of its modes and global context item.
122
+ * @param {(kind: string) => object[]} all - Declarations by kind
123
+ * @param {object} cx
124
+ */
125
+ export function declareAccumulators(all, cx) {
126
+ const chosen = highestPrecedence(
127
+ all("accumulator").map((declaration) => ({
128
+ ...declaration,
129
+ key: clarkOf(
130
+ cx.exprs.qname(
131
+ required(declaration.element, "name"),
132
+ declaration.element,
133
+ ),
134
+ ),
135
+ })),
136
+ );
137
+ cx.accumulators = new Map();
138
+ for (const [key, { element }] of chosen) {
139
+ cx.accumulators.set(key, compileAccumulator(element, cx, key));
140
+ }
141
+ declareModeAccumulators(all("mode"), cx);
142
+ const contextItem = all("global-context-item")[0]?.element;
143
+ const text = contextItem && attr(contextItem, "use-accumulators");
144
+ cx.globalAccumulators =
145
+ typeof text === "string" ? useAccumulators(text, contextItem, cx) : "all";
146
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * The values of the enumerated attributes of XSLT elements (XSLT 3.0
3
+ * section 3.5, XTSE0020): yes/no attributes (also true/false/1/0, with
4
+ * whitespace trimmed) and attributes with a fixed list of values;
5
+ * validation="strict" needs a schema-aware processor
6
+ * (XTSE1660).
7
+ *
8
+ * @module @tradik/xslt3/xslt/compiler/attributeValues
9
+ */
10
+
11
+ import { attr, xsltError } from "../names.js";
12
+
13
+ const BOOLEAN = ["yes", "no", "true", "false", "1", "0"];
14
+ const DECIMAL = /^[+-]?(\d+(\.\d*)?|\.\d+)$/;
15
+ const INTEGERS = /^[+-]?\d+(\s+[+-]?\d+)*$/;
16
+ const VALIDATION = ["strict", "lax", "preserve", "strip"];
17
+
18
+ /** Allowed values by element local name and attribute name. */
19
+ const VALUES = {
20
+ "attribute-set": { streamable: BOOLEAN },
21
+ attribute: { validation: VALIDATION },
22
+ copy: {
23
+ "copy-namespaces": BOOLEAN,
24
+ "inherit-namespaces": BOOLEAN,
25
+ validation: VALIDATION,
26
+ },
27
+ "copy-of": {
28
+ "copy-accumulators": BOOLEAN,
29
+ "copy-namespaces": BOOLEAN,
30
+ validation: VALIDATION,
31
+ },
32
+ document: { validation: VALIDATION },
33
+ element: { "inherit-namespaces": BOOLEAN, validation: VALIDATION },
34
+ "for-each-group": { composite: BOOLEAN },
35
+ number: { "start-at": INTEGERS },
36
+ output: { "html-version": DECIMAL },
37
+ "result-document": { "html-version": DECIMAL },
38
+ sort: { stable: BOOLEAN },
39
+ function: {
40
+ override: BOOLEAN,
41
+ "override-extension-function": BOOLEAN,
42
+ "new-each-time": [...BOOLEAN, "maybe"],
43
+ cache: BOOLEAN,
44
+ },
45
+ key: { composite: BOOLEAN },
46
+ mode: {
47
+ streamable: BOOLEAN,
48
+ typed: [...BOOLEAN, "strict", "lax", "unspecified"],
49
+ "warning-on-no-match": BOOLEAN,
50
+ "warning-on-multiple-match": BOOLEAN,
51
+ "on-multiple-match": ["use-last", "fail"],
52
+ },
53
+ package: { "declared-modes": BOOLEAN },
54
+ param: { required: BOOLEAN, tunnel: BOOLEAN, static: BOOLEAN },
55
+ stylesheet: {
56
+ "input-type-annotations": ["preserve", "strip", "unspecified"],
57
+ },
58
+ text: { "disable-output-escaping": BOOLEAN },
59
+ try: { "rollback-output": BOOLEAN },
60
+ "value-of": { "disable-output-escaping": BOOLEAN },
61
+ variable: { static: BOOLEAN },
62
+ "with-param": { tunnel: BOOLEAN },
63
+ };
64
+ VALUES.transform = VALUES.stylesheet;
65
+
66
+ /** Elements whose children are top-level declarations. */
67
+ const TOP_LEVEL = new Set(["stylesheet", "transform", "package"]);
68
+
69
+ /**
70
+ * Constraints that depend on where an element is or on another
71
+ * attribute: static only on global variables and parameters, function
72
+ * parameters neither tunnel nor optional, consistent override flags of
73
+ * xsl:function.
74
+ * @param {Element} element
75
+ */
76
+ function checkPlacement(element) {
77
+ const yes = (name) =>
78
+ ["yes", "true", "1"].includes(attr(element, name)?.trim());
79
+ const parent = element.parentNode;
80
+ const local = element.localName;
81
+ const fail = (message) => {
82
+ throw xsltError("XTSE0020", message);
83
+ };
84
+ if (yes("static") && !TOP_LEVEL.has(parent?.localName)) {
85
+ fail(`A local xsl:${local} cannot be static`);
86
+ }
87
+ if (
88
+ yes("static") &&
89
+ !["private", undefined].includes(attr(element, "visibility")?.trim())
90
+ ) {
91
+ fail("A static variable is private");
92
+ }
93
+ if (local === "param" && parent?.localName === "function") {
94
+ if (yes("tunnel")) {
95
+ fail("A function parameter cannot be a tunnel parameter");
96
+ }
97
+ if (attr(element, "required") !== undefined && !yes("required")) {
98
+ fail("A function parameter is always required");
99
+ }
100
+ }
101
+ const override = attr(element, "override");
102
+ const extension = attr(element, "override-extension-function");
103
+ if (
104
+ override !== undefined &&
105
+ extension !== undefined &&
106
+ yes("override") !== yes("override-extension-function")
107
+ ) {
108
+ fail("override and override-extension-function differ");
109
+ }
110
+ }
111
+
112
+ /**
113
+ * XTSE1660: what only a schema-aware processor accepts: a type
114
+ * attribute, strict validation, typed modes.
115
+ * @param {Element} element
116
+ */
117
+ function checkSchemaAware(element) {
118
+ const defaultValidation = attr(element, "default-validation")?.trim();
119
+ if (defaultValidation === "strict") {
120
+ throw xsltError("XTSE1660", "default-validation=strict needs a schema");
121
+ }
122
+ if (
123
+ defaultValidation !== undefined &&
124
+ defaultValidation !== "preserve" &&
125
+ defaultValidation !== "strip"
126
+ ) {
127
+ throw xsltError(
128
+ "XTSE0020",
129
+ `Invalid default-validation ${defaultValidation}`,
130
+ );
131
+ }
132
+ if (
133
+ VALUES[element.localName]?.validation &&
134
+ attr(element, "type") !== undefined
135
+ ) {
136
+ throw xsltError("XTSE1660", "The type attribute needs a schema");
137
+ }
138
+ if (
139
+ element.localName === "mode" &&
140
+ ["yes", "true", "1", "strict"].includes(attr(element, "typed")?.trim())
141
+ ) {
142
+ throw xsltError("XTSE1660", "A typed mode needs a schema");
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Checks the enumerated attributes of an XSLT element.
148
+ * @param {Element} element
149
+ * @throws {import("../../errors.js").XPathError} XTSE0020 for a value
150
+ * not allowed, XTSE1660 for validation that needs a schema
151
+ */
152
+ export function checkAttributeValues(element) {
153
+ const table = VALUES[element.localName];
154
+ for (const [name, allowed] of Object.entries(table ?? {})) {
155
+ const value = attr(element, name)?.trim();
156
+ // attribute value templates are checked when evaluated
157
+ if (value === undefined || value.includes("{")) continue;
158
+ const ok = Array.isArray(allowed)
159
+ ? allowed.includes(value)
160
+ : allowed.test(value);
161
+ if (!ok) {
162
+ throw xsltError(
163
+ "XTSE0020",
164
+ `Invalid value "${value}" for ${name} on xsl:${element.localName}`,
165
+ );
166
+ }
167
+ // XSLT 3.0: lax validation without a schema is allowed
168
+ if (name === "validation" && value === "strict") {
169
+ throw xsltError("XTSE1660", `validation="${value}" needs a schema`);
170
+ }
171
+ }
172
+ checkPlacement(element);
173
+ checkSchemaAware(element);
174
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * The attributes each XSLT element may have (XSLT 3.0 section 3.5,
3
+ * XTSE0090) and helpers to read them: required attributes (XTSE0010),
4
+ * yes/no values (XTSE0020).
5
+ *
6
+ * @module @tradik/xslt3/xslt/compiler/attributes
7
+ */
8
+
9
+ import { attr, XSL_NS, xsltError } from "../names.js";
10
+ import { checkAttributeValues } from "./attributeValues.js";
11
+ import { infoOf } from "./elementInfo.js";
12
+
13
+ /** Standard attributes, allowed on every XSLT element. */
14
+ const STANDARD = new Set([
15
+ "version",
16
+ "exclude-result-prefixes",
17
+ "extension-element-prefixes",
18
+ "xpath-default-namespace",
19
+ "default-collation",
20
+ "use-when",
21
+ "default-mode",
22
+ "default-validation",
23
+ "expand-text",
24
+ ]);
25
+
26
+ const SERIALIZATION =
27
+ "allow-duplicate-names build-tree byte-order-mark cdata-section-elements " +
28
+ "doctype-public doctype-system encoding escape-uri-attributes " +
29
+ "html-version include-content-type indent item-separator json-node-output-method " +
30
+ "media-type method normalization-form omit-xml-declaration parameter-document " +
31
+ "standalone suppress-indentation undeclare-prefixes use-character-maps";
32
+
33
+ /** Attributes by element local name. */
34
+ const ALLOWED = {
35
+ "analyze-string": "select regex flags",
36
+ "apply-imports": "",
37
+ "apply-templates": "select mode",
38
+ attribute: "name namespace select separator type validation",
39
+ "attribute-set": "name use-attribute-sets visibility streamable",
40
+ "call-template": "name",
41
+ "character-map": "name use-character-maps",
42
+ choose: "",
43
+ comment: "select",
44
+ copy: "select copy-namespaces inherit-namespaces use-attribute-sets type validation",
45
+ "copy-of": "select copy-accumulators copy-namespaces type validation",
46
+ "decimal-format":
47
+ "name decimal-separator grouping-separator infinity minus-sign exponent-separator NaN percent per-mille zero-digit digit pattern-separator",
48
+ document: "validation type",
49
+ element:
50
+ "name namespace inherit-namespaces use-attribute-sets type validation",
51
+ fallback: "",
52
+ "for-each": "select",
53
+ "for-each-group":
54
+ "select group-by group-adjacent group-starting-with group-ending-with composite collation",
55
+ function:
56
+ "name as visibility streamability override-extension-function override new-each-time cache",
57
+ if: "test",
58
+ import: "href",
59
+ "import-schema": "namespace schema-location",
60
+ include: "href",
61
+ key: "name match use composite collation",
62
+ "matching-substring": "",
63
+ map: "",
64
+ "map-entry": "key select",
65
+ try: "select rollback-output",
66
+ catch: "errors select",
67
+ "context-item": "as use",
68
+ "global-context-item": "as use use-accumulators",
69
+ message: "select terminate error-code",
70
+ "namespace-alias": "stylesheet-prefix result-prefix",
71
+ namespace: "name select",
72
+ "next-match": "",
73
+ "non-matching-substring": "",
74
+ number:
75
+ "value select level count from format lang letter-value ordinal start-at grouping-separator grouping-size",
76
+ otherwise: "",
77
+ output: `name ${SERIALIZATION}`,
78
+ "output-character": "character string",
79
+ param: "name select as required tunnel static",
80
+ "perform-sort": "select",
81
+ "preserve-space": "elements",
82
+ "processing-instruction": "name select",
83
+ "result-document": `format href type validation output-version ${SERIALIZATION}`,
84
+ sequence: "select",
85
+ sort: "select lang order collation stable case-order data-type",
86
+ "strip-space": "elements",
87
+ stylesheet: "id input-type-annotations",
88
+ template: "match name priority mode as visibility",
89
+ text: "disable-output-escaping",
90
+ transform: "id input-type-annotations",
91
+ "value-of": "select separator disable-output-escaping",
92
+ variable: "name select as visibility static",
93
+ when: "test",
94
+ "with-param": "name select as tunnel",
95
+ // XSLT 3.0 instructions of task 0031
96
+ break: "select",
97
+ iterate: "select",
98
+ "next-iteration": "",
99
+ "on-completion": "select",
100
+ "on-empty": "select",
101
+ "on-non-empty": "select",
102
+ "where-populated": "",
103
+ fork: "",
104
+ accumulator: "name initial-value as streamable",
105
+ "accumulator-rule": "match phase select",
106
+ "source-document": "href streamable use-accumulators validation type",
107
+ assert: "test select error-code",
108
+ evaluate:
109
+ "xpath as base-uri with-params context-item namespace-context schema-aware",
110
+ merge: "",
111
+ "merge-action": "",
112
+ "merge-key": "select lang order collation case-order data-type",
113
+ "merge-source":
114
+ "name for-each-item for-each-source select streamable use-accumulators sort-before-merge validation type",
115
+ package: "id name package-version input-type-annotations declared-modes",
116
+ "use-package": "name package-version",
117
+ expose: "component names visibility",
118
+ accept: "component names visibility",
119
+ override: "",
120
+ mode: "name streamable use-accumulators on-no-match on-multiple-match warning-on-no-match warning-on-multiple-match typed visibility",
121
+ };
122
+
123
+ /** @type {Map<string, Set<string>>} */
124
+ const allowedSets = new Map(
125
+ Object.entries(ALLOWED).map(([name, list]) => [
126
+ name,
127
+ new Set(list.split(" ").filter(Boolean)),
128
+ ]),
129
+ );
130
+
131
+ /**
132
+ * Checks the attributes of an XSLT element (unknown attributes are
133
+ * ignored in forwards-compatible mode).
134
+ * @param {Element} element
135
+ * @param {object} [_cx] - Stylesheet compiler (unused)
136
+ * @throws {import("../../errors.js").XPathError} XTSE0090
137
+ */
138
+ export function checkAttributes(element, _cx) {
139
+ const allowed = allowedSets.get(element.localName);
140
+ if (!allowed) return;
141
+ const forwards = infoOf(element).version > 3;
142
+ for (const attribute of element.attributes) {
143
+ const uri = attribute.namespaceURI;
144
+ const written = attribute.localName ?? attribute.name;
145
+ // a shadow attribute _name stands for the attribute name
146
+ const name = written.startsWith("_") ? written.slice(1) : written;
147
+ if (uri === XSL_NS) {
148
+ throw xsltError(
149
+ "XTSE0090",
150
+ `xsl:${element.localName} cannot have the attribute ${attribute.name}`,
151
+ );
152
+ }
153
+ if (uri || attribute.name.startsWith("xmlns")) continue;
154
+ if (allowed.has(name) || STANDARD.has(name) || forwards) continue;
155
+ throw xsltError(
156
+ "XTSE0090",
157
+ `xsl:${element.localName} cannot have the attribute ${name}`,
158
+ );
159
+ }
160
+ if (!forwards) checkAttributeValues(element);
161
+ }
162
+
163
+ /**
164
+ * Checks that an element that must be empty is.
165
+ * @param {Element} element
166
+ * @param {{children: (element: Element) => Array}} cx
167
+ * @throws {import("../../errors.js").XPathError} XTSE0260
168
+ */
169
+ export function checkEmpty(element, cx) {
170
+ if (cx.children(element).length > 0) {
171
+ throw xsltError("XTSE0260", `xsl:${element.localName} must be empty`);
172
+ }
173
+ }
174
+
175
+ /**
176
+ * A required attribute.
177
+ * @param {Element} element
178
+ * @param {string} name
179
+ * @returns {string}
180
+ * @throws {import("../../errors.js").XPathError} XTSE0010 when absent
181
+ */
182
+ export function required(element, name) {
183
+ const value = attr(element, name);
184
+ if (value === undefined) {
185
+ throw xsltError(
186
+ "XTSE0010",
187
+ `xsl:${element.localName} requires the attribute ${name}`,
188
+ );
189
+ }
190
+ return value;
191
+ }
192
+
193
+ /**
194
+ * A yes/no attribute (also true/false/1/0 in XSLT 3.0).
195
+ * @param {Element} element
196
+ * @param {string} name
197
+ * @param {boolean} fallback - Value when absent
198
+ * @returns {boolean}
199
+ * @throws {import("../../errors.js").XPathError} XTSE0020 for other values
200
+ */
201
+ export function yesNo(element, name, fallback) {
202
+ const value = attr(element, name);
203
+ if (value === undefined) return fallback;
204
+ const text = value.trim();
205
+ if (["yes", "true", "1"].includes(text)) return true;
206
+ if (["no", "false", "0"].includes(text)) return false;
207
+ throw xsltError(
208
+ "XTSE0020",
209
+ `Invalid value "${value}" for ${name} on xsl:${element.localName}`,
210
+ );
211
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Attribute value templates (XSLT 3.0 section 5.6.1): literal text with
3
+ * XPath expressions in curly brackets, `{{` and `}}` standing for single
4
+ * brackets. Also used for text value templates.
5
+ *
6
+ * @module @tradik/xslt3/xslt/compiler/avt
7
+ */
8
+
9
+ import { xsltError } from "../names.js";
10
+
11
+ /**
12
+ * Finds the end of an expression in curly brackets, skipping string
13
+ * literals, comments and nested brackets (map constructors).
14
+ * @param {string} text
15
+ * @param {number} start - Offset after the opening bracket
16
+ * @returns {number} offset of the closing bracket
17
+ * @throws {import("../../errors.js").XPathError} XTSE0350 when unclosed
18
+ */
19
+ function closingBracket(text, start) {
20
+ let depth = 0;
21
+ let i = start;
22
+ while (i < text.length) {
23
+ const c = text[i];
24
+ if (c === "'" || c === '"') {
25
+ const end = text.indexOf(c, i + 1);
26
+ if (end < 0) break;
27
+ i = end + 1;
28
+ continue;
29
+ }
30
+ if (c === "(" && text[i + 1] === ":") {
31
+ let level = 0;
32
+ while (i < text.length) {
33
+ if (text.startsWith("(:", i)) (level++, (i += 2));
34
+ else if (text.startsWith(":)", i)) {
35
+ i += 2;
36
+ if (--level === 0) break;
37
+ } else i++;
38
+ }
39
+ continue;
40
+ }
41
+ if (c === "{") depth++;
42
+ else if (c === "}") {
43
+ if (depth === 0) return i;
44
+ depth--;
45
+ }
46
+ i++;
47
+ }
48
+ throw xsltError("XTSE0350", `Unclosed expression in "${text}"`);
49
+ }
50
+
51
+ /**
52
+ * Removes the XPath comments (which nest) from a text.
53
+ * @param {string} text
54
+ * @returns {string}
55
+ */
56
+ export function withoutComments(text) {
57
+ let result = text;
58
+ for (let previous = ""; previous !== result;) {
59
+ previous = result;
60
+ result = result.replace(/\(:(?:(?!\(:|:\))[^])*:\)/g, "");
61
+ }
62
+ return result;
63
+ }
64
+
65
+ /**
66
+ * Splits an attribute value template.
67
+ * @param {string} text
68
+ * @returns {Array<string|{expr: string}>} literal parts and expressions
69
+ * @throws {import("../../errors.js").XPathError} XTSE0350 for an unclosed
70
+ * expression, XTSE0370 for a lone closing bracket
71
+ */
72
+ export function parseAvt(text) {
73
+ const parts = [];
74
+ let literal = "";
75
+ let i = 0;
76
+ while (i < text.length) {
77
+ const c = text[i];
78
+ if (c === "{" && text[i + 1] === "{") {
79
+ literal += "{";
80
+ i += 2;
81
+ } else if (c === "}" && text[i + 1] === "}") {
82
+ literal += "}";
83
+ i += 2;
84
+ } else if (c === "}") {
85
+ throw xsltError("XTSE0370", `Unescaped "}" in "${text}"`);
86
+ } else if (c === "{") {
87
+ const end = closingBracket(text, i + 1);
88
+ const expr = text.slice(i + 1, end);
89
+ // an empty expression (or only comments) stands for ""
90
+ if (withoutComments(expr).trim() !== "") {
91
+ if (literal) parts.push(literal);
92
+ literal = "";
93
+ parts.push({ expr });
94
+ }
95
+ i = end + 1;
96
+ } else {
97
+ literal += c;
98
+ i++;
99
+ }
100
+ }
101
+ if (literal || parts.length === 0) parts.push(literal);
102
+ return parts;
103
+ }