@tradik/xslt-processor 1.0.3 → 1.3.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 (131) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +110 -520
  3. package/bin/lib/decode.js +15 -0
  4. package/bin/lib/dom.js +177 -0
  5. package/bin/lib/loaders.js +127 -0
  6. package/bin/lib/options.js +131 -0
  7. package/bin/lib/output.js +114 -0
  8. package/bin/lib/paths.js +186 -0
  9. package/bin/lib/transform.js +206 -0
  10. package/bin/xslt.js +73 -168
  11. package/dist/xslt-processor.browser.js +9564 -1585
  12. package/dist/xslt-processor.browser.js.map +4 -4
  13. package/dist/xslt-processor.browser.min.js +13 -2
  14. package/dist/xslt-processor.browser.min.js.map +4 -4
  15. package/dist/xslt-processor.cjs +9572 -1586
  16. package/dist/xslt-processor.cjs.map +4 -4
  17. package/dist/xslt-processor.d.cts +658 -0
  18. package/dist/xslt-processor.d.ts +459 -12
  19. package/dist/xslt-processor.js +9546 -1582
  20. package/dist/xslt-processor.js.map +4 -4
  21. package/package.json +71 -20
  22. package/src/XSLTProcessor.js +494 -48
  23. package/src/async/abort.js +63 -0
  24. package/src/async/documentUris.js +128 -0
  25. package/src/async/loaders.js +134 -0
  26. package/src/async/preload.js +159 -0
  27. package/src/async/processor.js +206 -0
  28. package/src/async/stream.js +125 -0
  29. package/src/bridge/engine.js +221 -0
  30. package/src/bridge/loader.js +78 -0
  31. package/src/bridge/results.js +75 -0
  32. package/src/bridge/version.js +63 -0
  33. package/src/index.js +26 -8
  34. package/src/io/decode.js +140 -0
  35. package/src/io/readSource.js +167 -0
  36. package/src/xpath/axes.js +562 -0
  37. package/src/xpath/documentOrder.js +270 -0
  38. package/src/xpath/evaluator.js +518 -357
  39. package/src/xpath/index.js +8 -2
  40. package/src/xpath/namespaceNodes.js +172 -0
  41. package/src/xpath/nodeSetFunctions.js +169 -0
  42. package/src/xpath/parser.js +30 -5
  43. package/src/xpath/strings.js +183 -0
  44. package/src/xpath/tokenizer.js +37 -23
  45. package/src/xslt/attributeSets.js +95 -0
  46. package/src/xslt/avt.js +103 -0
  47. package/src/xslt/computedNames.js +91 -0
  48. package/src/xslt/copying.js +212 -0
  49. package/src/xslt/declarationNames.js +80 -0
  50. package/src/xslt/domParsing.js +95 -0
  51. package/src/xslt/elements.js +57 -0
  52. package/src/xslt/engine/bindings.js +195 -0
  53. package/src/xslt/engine/context.js +105 -0
  54. package/src/xslt/engine/controlFlow.js +145 -0
  55. package/src/xslt/engine/copyInstructions.js +133 -0
  56. package/src/xslt/engine/declarations.js +233 -0
  57. package/src/xslt/engine/functionSupport.js +103 -0
  58. package/src/xslt/engine/methods.js +33 -0
  59. package/src/xslt/engine/nodeConstruction.js +187 -0
  60. package/src/xslt/engine/numbering.js +104 -0
  61. package/src/xslt/engine/outputDeclaration.js +77 -0
  62. package/src/xslt/engine/sequenceConstructor.js +228 -0
  63. package/src/xslt/engine/stylesheetLoading.js +208 -0
  64. package/src/xslt/engine/templateInvocation.js +253 -0
  65. package/src/xslt/engine/templateRules.js +243 -0
  66. package/src/xslt/engine/textInstructions.js +171 -0
  67. package/src/xslt/engine/topLevel.js +130 -0
  68. package/src/xslt/engine/transformation.js +263 -0
  69. package/src/xslt/engine/workStack.js +245 -0
  70. package/src/xslt/engine.js +184 -1736
  71. package/src/xslt/exslt/arguments.js +99 -0
  72. package/src/xslt/exslt/calendar.js +120 -0
  73. package/src/xslt/exslt/common.js +44 -0
  74. package/src/xslt/exslt/dateCalc.js +261 -0
  75. package/src/xslt/exslt/dateFormat.js +150 -0
  76. package/src/xslt/exslt/dateParse.js +265 -0
  77. package/src/xslt/exslt/dates.js +259 -0
  78. package/src/xslt/exslt/duration.js +207 -0
  79. package/src/xslt/exslt/dynamic.js +59 -0
  80. package/src/xslt/exslt/index.js +59 -0
  81. package/src/xslt/exslt/math.js +177 -0
  82. package/src/xslt/exslt/sets.js +96 -0
  83. package/src/xslt/exslt/stringOps.js +163 -0
  84. package/src/xslt/exslt/strings.js +147 -0
  85. package/src/xslt/exslt/uri.js +92 -0
  86. package/src/xslt/formatNumber.js +233 -0
  87. package/src/xslt/forwardsCompatible.js +75 -0
  88. package/src/xslt/functions.js +270 -0
  89. package/src/xslt/index.js +38 -1
  90. package/src/xslt/keys.js +164 -0
  91. package/src/xslt/literalResult.js +223 -0
  92. package/src/xslt/matchScope.js +116 -0
  93. package/src/xslt/number.js +271 -0
  94. package/src/xslt/numberFormat.js +253 -0
  95. package/src/xslt/outputNames.js +58 -0
  96. package/src/xslt/patternCompiler.js +175 -0
  97. package/src/xslt/patterns.js +324 -0
  98. package/src/xslt/qname.js +90 -0
  99. package/src/xslt/resultDocument.js +98 -0
  100. package/src/xslt/resultNamespaces.js +219 -0
  101. package/src/xslt/resultTree.js +211 -0
  102. package/src/xslt/serializer/baseWriter.js +390 -0
  103. package/src/xslt/serializer/chunks.js +120 -0
  104. package/src/xslt/serializer/constants.js +92 -0
  105. package/src/xslt/serializer/encoding.js +327 -0
  106. package/src/xslt/serializer/escape.js +135 -0
  107. package/src/xslt/serializer/frames.js +168 -0
  108. package/src/xslt/serializer/htmlDoctype.js +102 -0
  109. package/src/xslt/serializer/htmlEntities.js +77 -0
  110. package/src/xslt/serializer/htmlSerializer.js +239 -0
  111. package/src/xslt/serializer/indent.js +51 -0
  112. package/src/xslt/serializer/namespaces.js +68 -0
  113. package/src/xslt/serializer/rawText.js +41 -0
  114. package/src/xslt/serializer/settings.js +179 -0
  115. package/src/xslt/serializer/textSerializer.js +77 -0
  116. package/src/xslt/serializer/xhtmlDocument.js +103 -0
  117. package/src/xslt/serializer/xmlSerializer.js +227 -0
  118. package/src/xslt/serializer.js +90 -0
  119. package/src/xslt/sort.js +151 -0
  120. package/src/xslt/spaceNameTests.js +115 -0
  121. package/src/xslt/stylesheetChecks.js +206 -0
  122. package/src/xslt/stylesheetNamespaces.js +266 -0
  123. package/src/xslt/templatePriority.js +45 -0
  124. package/src/xslt/uri.js +68 -0
  125. package/src/xslt/variables.js +152 -0
  126. package/src/xslt/whitespace.js +200 -0
  127. package/LICENSE +0 -29
  128. package/src/XSLTProcessor.test.js +0 -930
  129. package/src/xpath/evaluator.test.js +0 -1852
  130. package/src/xpath/tokenizer.test.js +0 -224
  131. package/src/xslt/engine.test.js +0 -3130
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Template invocation: xsl:apply-templates, xsl:apply-imports,
3
+ * xsl:call-template, the built-in template rules and template parameters.
4
+ *
5
+ * Methods installed on XsltEngine.prototype (`this` is the engine).
6
+ */
7
+
8
+ import { childAxis } from "../../xpath/axes.js";
9
+ import { requireExpandedName } from "../declarationNames.js";
10
+ import { selectNodes } from "./controlFlow.js";
11
+ import { LoopFrame, SequenceFrame } from "./workStack.js";
12
+
13
+ export const templateInvocationMethods = {
14
+ /**
15
+ * Apply templates to a node list. The nodes are processed one at a time
16
+ * from the work stack (see workStack.js).
17
+ *
18
+ * @param {Node|Node[]} nodes - The nodes to process, in order
19
+ * @param {string|null} mode - The mode
20
+ * @param {XsltContext} context - Context of the invoking instruction
21
+ * @param {Node} output - The result node receiving the output
22
+ * @param {Object<string, *>|null} [params] - Values of xsl:with-param by name
23
+ * @param {boolean} [builtin] - Whether a built-in template rule applies
24
+ * them, which counts as a template instantiation (as in libxslt)
25
+ * @returns {void}
26
+ */
27
+ applyTemplates(nodes, mode, context, output, params = null, builtin = false) {
28
+ const nodeList = Array.isArray(nodes) ? nodes : [nodes];
29
+ if (nodeList.length === 0) return;
30
+
31
+ const frame = new LoopFrame(nodeList.length, (index) =>
32
+ this.applyTemplateRule(nodeList, index, mode, context, output, params),
33
+ );
34
+ frame.template = builtin;
35
+ this.continueWith(frame);
36
+ },
37
+
38
+ /**
39
+ * Apply the best template rule to one node of a list, else the built-in
40
+ * rule.
41
+ *
42
+ * @param {Node[]} nodeList - The current node list
43
+ * @param {number} index - Index of the node in the list
44
+ * @param {string|null} mode - The mode
45
+ * @param {XsltContext} context - Context of the invoking instruction
46
+ * @param {Node} output - The result node receiving the output
47
+ * @param {Object<string, *>|null} params - Values of xsl:with-param by name
48
+ * @returns {void}
49
+ */
50
+ applyTemplateRule(nodeList, index, mode, context, output, params) {
51
+ const node = nodeList[index];
52
+ const template = this.findMatchingTemplate(node, mode, context);
53
+
54
+ if (template) {
55
+ const newContext = this.invocationContext(context, template, {
56
+ currentNode: node,
57
+ currentNodeList: nodeList,
58
+ position: index + 1,
59
+ currentMode: mode,
60
+ });
61
+
62
+ this.processTemplate(template.node, newContext, output, params);
63
+ } else {
64
+ this.applyBuiltinTemplate(node, mode, context, output, params);
65
+ }
66
+ },
67
+
68
+ /**
69
+ * Context of a template invocation: no local bindings, the template as
70
+ * current template and the prefixes in scope on the xsl:template element.
71
+ *
72
+ * @param {XsltContext} context - Context of the invoking instruction
73
+ * @param {object} template - The template record
74
+ * @param {object} [overrides] - Further properties to change
75
+ * @returns {XsltContext} The invocation context
76
+ */
77
+ invocationContext(context, template, overrides = {}) {
78
+ const invocation = context.clone({
79
+ currentTemplate: template,
80
+ ...overrides,
81
+ fresh: true,
82
+ });
83
+ invocation.namespaces = template.namespaces ?? context.namespaces;
84
+ return invocation;
85
+ },
86
+
87
+ /**
88
+ * Apply the built-in template rules (XSLT 1.0 section 5.8). Parameters are
89
+ * passed on to the templates applied to the children, as libxslt does.
90
+ *
91
+ * @param {Node} node - The node without a matching template
92
+ * @param {string|null} mode - The mode
93
+ * @param {XsltContext} context - Context of the invoking instruction
94
+ * @param {Node} output - The result node receiving the output
95
+ * @param {Object<string, *>|null} [params] - Values of xsl:with-param by name
96
+ * @returns {void}
97
+ */
98
+ applyBuiltinTemplate(node, mode, context, output, params = null) {
99
+ switch (node.nodeType) {
100
+ case 1: // Element
101
+ case 9: // Document
102
+ case 11: // Document Fragment
103
+ this.applyTemplates(
104
+ childAxis(node),
105
+ mode,
106
+ context,
107
+ output,
108
+ params,
109
+ true,
110
+ );
111
+ break;
112
+
113
+ case 2: // Attribute
114
+ case 3: // Text
115
+ case 4: // CDATA
116
+ // Copy the string value; a text node stands for its whole text run
117
+ output.appendChild(
118
+ context.outputDocument.createTextNode(
119
+ this.xpathEvaluator.getStringValue(node),
120
+ ),
121
+ );
122
+ break;
123
+
124
+ // Comments and PIs have no built-in template
125
+ }
126
+ },
127
+
128
+ /**
129
+ * Instantiate a template. Each `xsl:param` takes the value of the
130
+ * `xsl:with-param` of the same name, else its default, evaluated after the
131
+ * preceding parameters were bound (XSLT 1.0 section 11.6). The body runs
132
+ * from the work stack once the invoking instruction returns.
133
+ *
134
+ * @param {Element|object} templateNode - The xsl:template element
135
+ * @param {XsltContext} context - A fresh invocation context
136
+ * @param {Node} output - The result node receiving the output
137
+ * @param {Object<string, *>|null} [params] - Values of xsl:with-param by name
138
+ * @returns {void}
139
+ */
140
+ processTemplate(templateNode, context, output, params = null) {
141
+ for (
142
+ let child = templateNode.firstChild;
143
+ child;
144
+ child = child.nextSibling
145
+ ) {
146
+ if (child.nodeType !== 1 || !this.isXsltElement(child, "param")) continue;
147
+
148
+ const name = child.getAttribute("name");
149
+ context.parameters[name] =
150
+ params && Object.hasOwn(params, name)
151
+ ? params[name]
152
+ : this.evaluateVariable(
153
+ { node: child, select: child.getAttribute("select") },
154
+ context,
155
+ );
156
+ }
157
+
158
+ const frame = new SequenceFrame(this, templateNode, context, output);
159
+ frame.template = true;
160
+ this.continueWith(frame);
161
+ },
162
+
163
+ /**
164
+ * Instantiate `xsl:apply-templates`: the selected nodes (default the
165
+ * children), sorted by the xsl:sort children, with the xsl:with-param
166
+ * values.
167
+ *
168
+ * @param {Element} node - The xsl:apply-templates instruction
169
+ * @param {XsltContext} context - The current context
170
+ * @param {Node} output - The result node receiving the output
171
+ * @returns {void}
172
+ */
173
+ xslApplyTemplates(node, context, output) {
174
+ const select = node.getAttribute("select") || "node()";
175
+ const mode = node.getAttribute("mode") || null;
176
+ const nodes = this.sortNodes(
177
+ selectNodes(this, select, context),
178
+ this.sortElementsOf(node),
179
+ context,
180
+ );
181
+ const params = this.withParams(node, context);
182
+ this.applyTemplates(nodes, mode, context, output, params);
183
+ },
184
+
185
+ /**
186
+ * Instantiate `xsl:apply-imports`.
187
+ *
188
+ * Only templates with a lower import precedence than the template being
189
+ * instantiated are considered; when none matches, the built-in template rules
190
+ * apply, exactly as for `xsl:apply-templates`.
191
+ *
192
+ * @param {Element} node - The `xsl:apply-imports` element
193
+ * @param {XsltContext} context - The current XSLT context
194
+ * @param {Node} output - The result tree node receiving the output
195
+ * @returns {void}
196
+ */
197
+ xslApplyImports(node, context, output) {
198
+ const currentNode = context.currentNode;
199
+ const mode = context.currentMode ?? null;
200
+ const precedence = context.currentTemplate
201
+ ? context.currentTemplate.importPrecedence || 0
202
+ : 0;
203
+
204
+ const template = this.findMatchingTemplate(
205
+ currentNode,
206
+ mode,
207
+ context,
208
+ precedence,
209
+ );
210
+
211
+ if (!template) {
212
+ this.applyBuiltinTemplate(currentNode, mode, context, output);
213
+ return;
214
+ }
215
+
216
+ // xsl:apply-imports passes no parameters (XSLT 1.0 section 5.6)
217
+ this.processTemplate(
218
+ template.node,
219
+ this.invocationContext(context, template),
220
+ output,
221
+ );
222
+ },
223
+
224
+ /**
225
+ * Instantiate `xsl:call-template` with its xsl:with-param values.
226
+ *
227
+ * @param {Element} node - The xsl:call-template instruction
228
+ * @param {XsltContext} context - The current context
229
+ * @param {Node} output - The result node receiving the output
230
+ * @returns {void}
231
+ * @throws {Error} When no template has that name
232
+ */
233
+ xslCallTemplate(node, context, output) {
234
+ const name = node.getAttribute("name");
235
+ const template = this.findNamedTemplate(
236
+ requireExpandedName(name ?? "", node, "xsl:call-template name"),
237
+ );
238
+ if (!template) {
239
+ throw new Error(`Template not found: ${name}`);
240
+ }
241
+
242
+ // A named template does not become the current template rule
243
+ const invocation = this.invocationContext(context, template, {
244
+ currentTemplate: context.currentTemplate,
245
+ });
246
+ this.processTemplate(
247
+ template.node,
248
+ invocation,
249
+ output,
250
+ this.withParams(node, context),
251
+ );
252
+ },
253
+ };
@@ -0,0 +1,243 @@
1
+ /**
2
+ * The template registry: registration of xsl:template, default priorities,
3
+ * and the choice of the template rule or named template to instantiate.
4
+ *
5
+ * Methods installed on XsltEngine.prototype (`this` is the engine).
6
+ */
7
+
8
+ import { calculatePriority } from "../templatePriority.js";
9
+ import { inScopeNamespaces } from "../stylesheetNamespaces.js";
10
+ import { requireExpandedName } from "../declarationNames.js";
11
+ import { checkLocalBindings, checkPattern } from "../stylesheetChecks.js";
12
+
13
+ /**
14
+ * Split a pattern on the `|` operators that are not inside a predicate or a
15
+ * string literal.
16
+ *
17
+ * @param {string} pattern - The match pattern
18
+ * @returns {string[]} The alternatives, untrimmed; a trailing empty one is
19
+ * dropped
20
+ */
21
+ export function splitUnionPattern(pattern) {
22
+ const parts = [];
23
+ let start = 0;
24
+ let depth = 0;
25
+ let quote = "";
26
+
27
+ for (let i = 0; i < pattern.length; i++) {
28
+ const char = pattern[i];
29
+ if (quote) {
30
+ if (char === quote) quote = "";
31
+ } else if (char === '"' || char === "'") {
32
+ quote = char;
33
+ } else if (char === "[") {
34
+ depth++;
35
+ } else if (char === "]") {
36
+ depth--;
37
+ } else if (char === "|" && depth === 0) {
38
+ parts.push(pattern.slice(start, i));
39
+ start = i + 1;
40
+ }
41
+ }
42
+
43
+ if (start < pattern.length) parts.push(pattern.slice(start));
44
+ return parts;
45
+ }
46
+
47
+ /**
48
+ * Whether a template rule may be chosen for a mode below an import
49
+ * precedence limit.
50
+ *
51
+ * @param {object} template - The template record
52
+ * @param {string|null} mode - The mode
53
+ * @param {number} maxImportPrecedence - Exclusive import precedence limit
54
+ * @returns {boolean} True when the template is a candidate
55
+ */
56
+ function isCandidateRule(template, mode, maxImportPrecedence) {
57
+ return (
58
+ template.mode === mode &&
59
+ Boolean(template.match) &&
60
+ (template.importPrecedence || 0) < maxImportPrecedence
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Whether a matching template rule wins over the best one so far: a higher
66
+ * import precedence wins; on equal precedence a priority that is not lower
67
+ * wins, so the last template in stylesheet order wins ties (the XSLT 1.0
68
+ * section 5.5 recovery, as in libxslt).
69
+ *
70
+ * @param {object} template - The matching template
71
+ * @param {object|null} best - The best template so far
72
+ * @returns {boolean} True when `template` becomes the best one
73
+ */
74
+ function outranks(template, best) {
75
+ if (!best) return true;
76
+ const precedence = template.importPrecedence || 0;
77
+ const bestPrecedence = best.importPrecedence || 0;
78
+ return (
79
+ precedence > bestPrecedence ||
80
+ (precedence === bestPrecedence && template.priority >= best.priority)
81
+ );
82
+ }
83
+
84
+ export const templateRuleMethods = {
85
+ /**
86
+ * Register a template rule.
87
+ *
88
+ * A union match pattern is equivalent to a set of template rules, one per
89
+ * alternative (XSLT 1.0 section 5.5), so each alternative is registered
90
+ * separately with its own default priority.
91
+ *
92
+ * @param {Element} node - The xsl:template element
93
+ * @returns {void}
94
+ */
95
+ registerTemplate(node) {
96
+ const match = node.getAttribute("match");
97
+ if (match !== null) {
98
+ checkPattern(this.patternMatcher, match, "xsl:template match");
99
+ }
100
+ checkLocalBindings(node, (message) => this.warnOnce(message));
101
+ const name = node.getAttribute("name");
102
+ const nameKey = name === null ? null : this.namedTemplateKey(node, name);
103
+ const mode = node.getAttribute("mode") || null;
104
+ const priorityAttr = node.getAttribute("priority");
105
+ const alternatives = match
106
+ ? this.splitUnionPattern(match).map((p) => p.trim())
107
+ : [null];
108
+
109
+ const namespaces = inScopeNamespaces(node);
110
+ for (const alternative of alternatives) {
111
+ this.templates.push({
112
+ match: alternative,
113
+ name,
114
+ nameKey,
115
+ mode,
116
+ namespaces,
117
+ priority: priorityAttr
118
+ ? parseFloat(priorityAttr)
119
+ : this.calculatePriority(alternative),
120
+ importPrecedence: this.currentImportPrecedence,
121
+ node,
122
+ });
123
+ }
124
+ },
125
+
126
+ /**
127
+ * Expand the name of a named template, rejecting a second template of the
128
+ * same expanded name and import precedence (XSLT 1.0 section 6), as
129
+ * libxslt does.
130
+ *
131
+ * @param {Element} node - The xsl:template element
132
+ * @param {string} name - Its name attribute
133
+ * @returns {string} The expanded name key
134
+ * @throws {Error} When the name is invalid or already used
135
+ */
136
+ namedTemplateKey(node, name) {
137
+ const key = requireExpandedName(name, node, "xsl:template name");
138
+ const duplicate = this.templates.some(
139
+ (template) =>
140
+ template.nameKey === key &&
141
+ template.importPrecedence === this.currentImportPrecedence,
142
+ );
143
+ if (duplicate) {
144
+ throw new Error(
145
+ `xsl:template: duplicate template name "${name}" at the same import precedence (XSLT 1.0 section 6)`,
146
+ );
147
+ }
148
+ return key;
149
+ },
150
+
151
+ /**
152
+ * Default priority of a single match pattern (see templatePriority.js).
153
+ *
154
+ * @param {string|null} matchPattern - The match pattern
155
+ * @returns {number} The default priority
156
+ */
157
+ calculatePriority(matchPattern) {
158
+ return calculatePriority(matchPattern ? matchPattern.trim() : matchPattern);
159
+ },
160
+
161
+ /**
162
+ * Split a union pattern into its alternatives (see splitUnionPattern).
163
+ *
164
+ * @param {string} pattern - The match pattern
165
+ * @returns {string[]} The alternatives
166
+ */
167
+ splitUnionPattern(pattern) {
168
+ return splitUnionPattern(pattern);
169
+ },
170
+
171
+ /**
172
+ * Find the best matching template rule for a node (XSLT 1.0 section 5.5).
173
+ * Every candidate's pattern is tested, in stylesheet order.
174
+ *
175
+ * @param {Node} node - The node to process
176
+ * @param {string|null} mode - The mode
177
+ * @param {XsltContext} context - Context supplying variables
178
+ * @param {number} [maxImportPrecedence] - Only templates of a lower import
179
+ * precedence are considered (xsl:apply-imports)
180
+ * @returns {object|null} The template, or null for the built-in rules
181
+ */
182
+ findMatchingTemplate(node, mode, context, maxImportPrecedence = Infinity) {
183
+ let best = null;
184
+ for (const template of this.templates) {
185
+ if (
186
+ isCandidateRule(template, mode, maxImportPrecedence) &&
187
+ this.matchesPattern(
188
+ node,
189
+ template.match,
190
+ context,
191
+ template.namespaces,
192
+ ) &&
193
+ outranks(template, best)
194
+ ) {
195
+ best = template;
196
+ }
197
+ }
198
+ return best;
199
+ },
200
+
201
+ /**
202
+ * Find the named template with the highest import precedence; among equal
203
+ * precedences the last one in stylesheet order wins.
204
+ *
205
+ * @param {string} key - Expanded name of the template (see
206
+ * declarationNames.js)
207
+ * @returns {object|null} The template, or null when none has that name
208
+ */
209
+ findNamedTemplate(key) {
210
+ let best = null;
211
+ for (const template of this.templates) {
212
+ if (
213
+ template.nameKey === key &&
214
+ (!best ||
215
+ (template.importPrecedence || 0) >= (best.importPrecedence || 0))
216
+ ) {
217
+ best = template;
218
+ }
219
+ }
220
+ return best;
221
+ },
222
+
223
+ /**
224
+ * Check whether a node matches an XSLT pattern (see patterns.js).
225
+ *
226
+ * Errors raised while evaluating a predicate make the pattern not match,
227
+ * as before the compiled matcher existed.
228
+ *
229
+ * @param {Node} node - The candidate node
230
+ * @param {string} pattern - The XSLT pattern
231
+ * @param {XsltContext|null} context - Context supplying variables and namespaces
232
+ * @param {Object<string, string>} [namespaces] - Prefixes in scope on the
233
+ * element holding the pattern, when they differ from the context's
234
+ * @returns {boolean} Whether the node matches
235
+ */
236
+ matchesPattern(node, pattern, context, namespaces) {
237
+ try {
238
+ return this.patternMatcher.matches(node, pattern, context, namespaces);
239
+ } catch {
240
+ return false;
241
+ }
242
+ },
243
+ };
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Instructions producing text: xsl:value-of, xsl:text, xsl:comment,
3
+ * xsl:processing-instruction and xsl:message, and attribute value templates.
4
+ *
5
+ * Methods installed on XsltEngine.prototype (`this` is the engine).
6
+ */
7
+
8
+ import { evaluateAvt } from "../avt.js";
9
+
10
+ /**
11
+ * Append a text node to the result unless the text is empty.
12
+ *
13
+ * @param {XsltContext} context - The current context
14
+ * @param {Node} output - The result node receiving the text
15
+ * @param {string} text - The text
16
+ * @param {boolean} disableOutputEscaping - Whether the serializer must write
17
+ * the text without escaping
18
+ * @returns {void}
19
+ */
20
+ function appendText(context, output, text, disableOutputEscaping) {
21
+ if (!text) return;
22
+ const textNode = context.outputDocument.createTextNode(text);
23
+ if (disableOutputEscaping) {
24
+ textNode._disableOutputEscaping = true;
25
+ }
26
+ output.appendChild(textNode);
27
+ }
28
+
29
+ /**
30
+ * Whether an instruction has disable-output-escaping="yes".
31
+ *
32
+ * @param {Element} node - xsl:value-of or xsl:text
33
+ * @returns {boolean} True when output escaping is disabled
34
+ */
35
+ function disablesOutputEscaping(node) {
36
+ return node.getAttribute("disable-output-escaping") === "yes";
37
+ }
38
+
39
+ export const textInstructionMethods = {
40
+ /**
41
+ * Instantiate `xsl:value-of`: the string value of the expression.
42
+ *
43
+ * @param {Element} node - The xsl:value-of instruction
44
+ * @param {XsltContext} context - The current context
45
+ * @param {Node} output - The result node receiving the text
46
+ * @returns {void}
47
+ */
48
+ xslValueOf(node, context, output) {
49
+ const result = this.evaluateXPath(node.getAttribute("select"), context);
50
+ appendText(
51
+ context,
52
+ output,
53
+ this.xpathEvaluator.toString(result),
54
+ disablesOutputEscaping(node),
55
+ );
56
+ },
57
+
58
+ /**
59
+ * Instantiate `xsl:text`: its text and CDATA children.
60
+ *
61
+ * @param {Element} node - The xsl:text instruction
62
+ * @param {XsltContext} context - The current context
63
+ * @param {Node} output - The result node receiving the text
64
+ * @returns {void}
65
+ */
66
+ xslText(node, context, output) {
67
+ let text = "";
68
+ for (const child of node.childNodes) {
69
+ if (child.nodeType === 3 || child.nodeType === 4) {
70
+ text += child.nodeValue || "";
71
+ }
72
+ }
73
+ appendText(context, output, text, disablesOutputEscaping(node));
74
+ },
75
+
76
+ /**
77
+ * Instantiate the content of an instruction and return its string value,
78
+ * for instructions whose result is text (attribute, comment, PI, message).
79
+ *
80
+ * @param {Element} node - The instruction
81
+ * @param {XsltContext} context - The current context
82
+ * @returns {string} The text of the instantiated content
83
+ */
84
+ instantiateText(node, context) {
85
+ const fragment = context.outputDocument.createDocumentFragment();
86
+ this.processChildren(node, context, fragment);
87
+ return this.xpathEvaluator.getStringValue(fragment);
88
+ },
89
+
90
+ /**
91
+ * Instantiate `xsl:comment`. "--" and a trailing "-" are made safe by the
92
+ * serializer (XSLT 1.0 section 7.4).
93
+ *
94
+ * @param {Element} node - The xsl:comment instruction
95
+ * @param {XsltContext} context - The current context
96
+ * @param {Node} output - The result node receiving the comment
97
+ * @returns {void}
98
+ */
99
+ xslComment(node, context, output) {
100
+ const text = this.instantiateText(node, context);
101
+ output.appendChild(context.outputDocument.createComment(text));
102
+ },
103
+
104
+ /**
105
+ * Instantiate `xsl:processing-instruction`, with the error recovery of
106
+ * XSLT 1.0 section 7.3: "?>" cannot end the data early.
107
+ *
108
+ * @param {Element} node - The xsl:processing-instruction instruction
109
+ * @param {XsltContext} context - The current context
110
+ * @param {Node} output - The result node receiving the PI
111
+ * @returns {void}
112
+ */
113
+ xslProcessingInstruction(node, context, output) {
114
+ const name = this.processAttributeValueTemplate(
115
+ node.getAttribute("name"),
116
+ context,
117
+ );
118
+ const data = this.instantiateText(node, context).replaceAll("?>", "? >");
119
+ const pi = context.outputDocument.createProcessingInstruction(name, data);
120
+ output.appendChild(pi);
121
+ },
122
+
123
+ /**
124
+ * Instantiate `xsl:message`: log the text, and stop the transformation
125
+ * with terminate="yes".
126
+ *
127
+ * @param {Element} node - The xsl:message instruction
128
+ * @param {XsltContext} context - The current context
129
+ * @param {Node} _output - Unused: a message produces no output
130
+ * @returns {void}
131
+ * @throws {Error} With terminate="yes"
132
+ */
133
+ xslMessage(node, context, _output) {
134
+ const terminate = node.getAttribute("terminate") === "yes";
135
+ const text = this.instantiateText(node, context);
136
+
137
+ console.log("XSLT Message:", text);
138
+
139
+ if (terminate) {
140
+ throw new Error(`XSLT terminated: ${text}`);
141
+ }
142
+ },
143
+
144
+ /**
145
+ * Evaluate an attribute value template (see avt.js).
146
+ *
147
+ * @param {string} value - The attribute value
148
+ * @param {XsltContext} context - The current context
149
+ * @returns {string} The resulting text
150
+ */
151
+ processAttributeValueTemplate(value, context) {
152
+ return evaluateAvt(value, (expr) =>
153
+ this.xpathEvaluator.toString(this.evaluateXPath(expr, context)),
154
+ );
155
+ },
156
+
157
+ /**
158
+ * Evaluate an optional attribute value template attribute.
159
+ *
160
+ * @param {Element} node - The instruction
161
+ * @param {string} name - The attribute name
162
+ * @param {XsltContext} context - The current context
163
+ * @returns {string|null} The value, or null when the attribute is absent
164
+ */
165
+ optionalAvt(node, name, context) {
166
+ const raw = node.getAttribute(name);
167
+ return raw === null
168
+ ? null
169
+ : this.processAttributeValueTemplate(raw, context);
170
+ },
171
+ };