@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.
- package/LICENSE.md +1 -1
- package/README.md +110 -520
- package/bin/lib/decode.js +15 -0
- package/bin/lib/dom.js +177 -0
- package/bin/lib/loaders.js +127 -0
- package/bin/lib/options.js +131 -0
- package/bin/lib/output.js +114 -0
- package/bin/lib/paths.js +186 -0
- package/bin/lib/transform.js +206 -0
- package/bin/xslt.js +73 -168
- package/dist/xslt-processor.browser.js +9564 -1585
- package/dist/xslt-processor.browser.js.map +4 -4
- package/dist/xslt-processor.browser.min.js +13 -2
- package/dist/xslt-processor.browser.min.js.map +4 -4
- package/dist/xslt-processor.cjs +9572 -1586
- package/dist/xslt-processor.cjs.map +4 -4
- package/dist/xslt-processor.d.cts +658 -0
- package/dist/xslt-processor.d.ts +459 -12
- package/dist/xslt-processor.js +9546 -1582
- package/dist/xslt-processor.js.map +4 -4
- package/package.json +71 -20
- package/src/XSLTProcessor.js +494 -48
- package/src/async/abort.js +63 -0
- package/src/async/documentUris.js +128 -0
- package/src/async/loaders.js +134 -0
- package/src/async/preload.js +159 -0
- package/src/async/processor.js +206 -0
- package/src/async/stream.js +125 -0
- package/src/bridge/engine.js +221 -0
- package/src/bridge/loader.js +78 -0
- package/src/bridge/results.js +75 -0
- package/src/bridge/version.js +63 -0
- package/src/index.js +26 -8
- package/src/io/decode.js +140 -0
- package/src/io/readSource.js +167 -0
- package/src/xpath/axes.js +562 -0
- package/src/xpath/documentOrder.js +270 -0
- package/src/xpath/evaluator.js +518 -357
- package/src/xpath/index.js +8 -2
- package/src/xpath/namespaceNodes.js +172 -0
- package/src/xpath/nodeSetFunctions.js +169 -0
- package/src/xpath/parser.js +30 -5
- package/src/xpath/strings.js +183 -0
- package/src/xpath/tokenizer.js +37 -23
- package/src/xslt/attributeSets.js +95 -0
- package/src/xslt/avt.js +103 -0
- package/src/xslt/computedNames.js +91 -0
- package/src/xslt/copying.js +212 -0
- package/src/xslt/declarationNames.js +80 -0
- package/src/xslt/domParsing.js +95 -0
- package/src/xslt/elements.js +57 -0
- package/src/xslt/engine/bindings.js +195 -0
- package/src/xslt/engine/context.js +105 -0
- package/src/xslt/engine/controlFlow.js +145 -0
- package/src/xslt/engine/copyInstructions.js +133 -0
- package/src/xslt/engine/declarations.js +233 -0
- package/src/xslt/engine/functionSupport.js +103 -0
- package/src/xslt/engine/methods.js +33 -0
- package/src/xslt/engine/nodeConstruction.js +187 -0
- package/src/xslt/engine/numbering.js +104 -0
- package/src/xslt/engine/outputDeclaration.js +77 -0
- package/src/xslt/engine/sequenceConstructor.js +228 -0
- package/src/xslt/engine/stylesheetLoading.js +208 -0
- package/src/xslt/engine/templateInvocation.js +253 -0
- package/src/xslt/engine/templateRules.js +243 -0
- package/src/xslt/engine/textInstructions.js +171 -0
- package/src/xslt/engine/topLevel.js +130 -0
- package/src/xslt/engine/transformation.js +263 -0
- package/src/xslt/engine/workStack.js +245 -0
- package/src/xslt/engine.js +184 -1736
- package/src/xslt/exslt/arguments.js +99 -0
- package/src/xslt/exslt/calendar.js +120 -0
- package/src/xslt/exslt/common.js +44 -0
- package/src/xslt/exslt/dateCalc.js +261 -0
- package/src/xslt/exslt/dateFormat.js +150 -0
- package/src/xslt/exslt/dateParse.js +265 -0
- package/src/xslt/exslt/dates.js +259 -0
- package/src/xslt/exslt/duration.js +207 -0
- package/src/xslt/exslt/dynamic.js +59 -0
- package/src/xslt/exslt/index.js +59 -0
- package/src/xslt/exslt/math.js +177 -0
- package/src/xslt/exslt/sets.js +96 -0
- package/src/xslt/exslt/stringOps.js +163 -0
- package/src/xslt/exslt/strings.js +147 -0
- package/src/xslt/exslt/uri.js +92 -0
- package/src/xslt/formatNumber.js +233 -0
- package/src/xslt/forwardsCompatible.js +75 -0
- package/src/xslt/functions.js +270 -0
- package/src/xslt/index.js +38 -1
- package/src/xslt/keys.js +164 -0
- package/src/xslt/literalResult.js +223 -0
- package/src/xslt/matchScope.js +116 -0
- package/src/xslt/number.js +271 -0
- package/src/xslt/numberFormat.js +253 -0
- package/src/xslt/outputNames.js +58 -0
- package/src/xslt/patternCompiler.js +175 -0
- package/src/xslt/patterns.js +324 -0
- package/src/xslt/qname.js +90 -0
- package/src/xslt/resultDocument.js +98 -0
- package/src/xslt/resultNamespaces.js +219 -0
- package/src/xslt/resultTree.js +211 -0
- package/src/xslt/serializer/baseWriter.js +390 -0
- package/src/xslt/serializer/chunks.js +120 -0
- package/src/xslt/serializer/constants.js +92 -0
- package/src/xslt/serializer/encoding.js +327 -0
- package/src/xslt/serializer/escape.js +135 -0
- package/src/xslt/serializer/frames.js +168 -0
- package/src/xslt/serializer/htmlDoctype.js +102 -0
- package/src/xslt/serializer/htmlEntities.js +77 -0
- package/src/xslt/serializer/htmlSerializer.js +239 -0
- package/src/xslt/serializer/indent.js +51 -0
- package/src/xslt/serializer/namespaces.js +68 -0
- package/src/xslt/serializer/rawText.js +41 -0
- package/src/xslt/serializer/settings.js +179 -0
- package/src/xslt/serializer/textSerializer.js +77 -0
- package/src/xslt/serializer/xhtmlDocument.js +103 -0
- package/src/xslt/serializer/xmlSerializer.js +227 -0
- package/src/xslt/serializer.js +90 -0
- package/src/xslt/sort.js +151 -0
- package/src/xslt/spaceNameTests.js +115 -0
- package/src/xslt/stylesheetChecks.js +206 -0
- package/src/xslt/stylesheetNamespaces.js +266 -0
- package/src/xslt/templatePriority.js +45 -0
- package/src/xslt/uri.js +68 -0
- package/src/xslt/variables.js +152 -0
- package/src/xslt/whitespace.js +200 -0
- package/LICENSE +0 -29
- package/src/XSLTProcessor.test.js +0 -930
- package/src/xpath/evaluator.test.js +0 -1852
- package/src/xpath/tokenizer.test.js +0 -224
- 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
|
+
};
|