@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,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copying source nodes to the result: xsl:copy and xsl:copy-of.
|
|
3
|
+
*
|
|
4
|
+
* Methods installed on XsltEngine.prototype (`this` is the engine).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
cloneNode,
|
|
9
|
+
copyAttribute,
|
|
10
|
+
copyNamespaceNode,
|
|
11
|
+
copyOf,
|
|
12
|
+
shallowCopyElement,
|
|
13
|
+
} from "../copying.js";
|
|
14
|
+
|
|
15
|
+
export const copyInstructionMethods = {
|
|
16
|
+
/**
|
|
17
|
+
* Instantiate `xsl:copy`: a shallow copy of the current node; the content
|
|
18
|
+
* is instantiated into a copied element, or directly for a root node.
|
|
19
|
+
*
|
|
20
|
+
* @param {Element} node - The xsl:copy instruction
|
|
21
|
+
* @param {XsltContext} context - The current context
|
|
22
|
+
* @param {Node} output - The result node receiving the copy
|
|
23
|
+
* @returns {void}
|
|
24
|
+
*/
|
|
25
|
+
xslCopy(node, context, output) {
|
|
26
|
+
const currentNode = context.currentNode;
|
|
27
|
+
const useAttributeSets = node.getAttribute("use-attribute-sets");
|
|
28
|
+
|
|
29
|
+
switch (currentNode.nodeType) {
|
|
30
|
+
case 1: {
|
|
31
|
+
// Element, with its namespace declarations (XSLT 1.0 section 7.5)
|
|
32
|
+
const copy = shallowCopyElement(currentNode, context.outputDocument);
|
|
33
|
+
output.appendChild(copy);
|
|
34
|
+
|
|
35
|
+
if (useAttributeSets) {
|
|
36
|
+
this.applyAttributeSets(useAttributeSets, context, copy, node);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.scheduleChildren(node, context, copy);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
case 2: // Attribute
|
|
44
|
+
copyAttribute(currentNode, output, (target) =>
|
|
45
|
+
this.canAddAttribute(target),
|
|
46
|
+
);
|
|
47
|
+
break;
|
|
48
|
+
|
|
49
|
+
case 13: // Namespace node: a namespace declaration
|
|
50
|
+
copyNamespaceNode(currentNode, output, (target) =>
|
|
51
|
+
this.canAddAttribute(target),
|
|
52
|
+
);
|
|
53
|
+
break;
|
|
54
|
+
|
|
55
|
+
case 3: // Text
|
|
56
|
+
case 4: // CDATA; a text node stands for its whole text run
|
|
57
|
+
output.appendChild(
|
|
58
|
+
context.outputDocument.createTextNode(
|
|
59
|
+
this.xpathEvaluator.getStringValue(currentNode),
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case 7: // Processing Instruction
|
|
65
|
+
output.appendChild(
|
|
66
|
+
context.outputDocument.createProcessingInstruction(
|
|
67
|
+
currentNode.target,
|
|
68
|
+
currentNode.data,
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
case 8: // Comment
|
|
74
|
+
output.appendChild(
|
|
75
|
+
context.outputDocument.createComment(currentNode.nodeValue || ""),
|
|
76
|
+
);
|
|
77
|
+
break;
|
|
78
|
+
|
|
79
|
+
case 9: // Document
|
|
80
|
+
case 11: // Document Fragment
|
|
81
|
+
this.scheduleChildren(node, context, output);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Instantiate `xsl:copy-of`: a deep copy of the selected value.
|
|
88
|
+
*
|
|
89
|
+
* @param {Element} node - The xsl:copy-of instruction
|
|
90
|
+
* @param {XsltContext} context - The current context
|
|
91
|
+
* @param {Node} output - The result node receiving the copy
|
|
92
|
+
* @returns {void}
|
|
93
|
+
*/
|
|
94
|
+
xslCopyOf(node, context, output) {
|
|
95
|
+
const select = node.getAttribute("select");
|
|
96
|
+
const result = this.evaluateXPath(select, context);
|
|
97
|
+
|
|
98
|
+
this.copyToOutput(result, context, output);
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Copy a value to the result tree as `xsl:copy-of` does (see copying.js).
|
|
103
|
+
*
|
|
104
|
+
* @param {*} value - The value to copy
|
|
105
|
+
* @param {XsltContext} context - The current context
|
|
106
|
+
* @param {Node} output - The result node receiving the copy
|
|
107
|
+
* @returns {void}
|
|
108
|
+
*/
|
|
109
|
+
copyToOutput(value, context, output) {
|
|
110
|
+
copyOf(value, output, {
|
|
111
|
+
doc: context.outputDocument,
|
|
112
|
+
stringValue: (node) => this.xpathEvaluator.getStringValue(node),
|
|
113
|
+
toString: (item) => this.xpathEvaluator.toString(item),
|
|
114
|
+
canAddAttribute: (target) => this.canAddAttribute(target),
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Deep copy of a node for the result tree (see copying.js); nodes that
|
|
120
|
+
* cannot be children become an empty text node.
|
|
121
|
+
*
|
|
122
|
+
* @param {Node} node - The node to copy
|
|
123
|
+
* @param {Document} targetDoc - The result document
|
|
124
|
+
* @returns {Node} The copy
|
|
125
|
+
*/
|
|
126
|
+
deepCloneNode(node, targetDoc) {
|
|
127
|
+
return (
|
|
128
|
+
cloneNode(node, targetDoc, (text) =>
|
|
129
|
+
this.xpathEvaluator.getStringValue(text),
|
|
130
|
+
) ?? targetDoc.createTextNode("")
|
|
131
|
+
);
|
|
132
|
+
},
|
|
133
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Top-level declarations other than templates and xsl:output: global
|
|
3
|
+
* variables and parameters, keys, decimal formats, namespace aliases,
|
|
4
|
+
* attribute sets and whitespace stripping.
|
|
5
|
+
*
|
|
6
|
+
* Methods installed on XsltEngine.prototype (`this` is the engine).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { inScopeNamespaces } from "../stylesheetNamespaces.js";
|
|
10
|
+
import { compileSpaceNameTests } from "../spaceNameTests.js";
|
|
11
|
+
import { registerAttributeSet } from "../attributeSets.js";
|
|
12
|
+
import { expandName } from "../declarationNames.js";
|
|
13
|
+
import {
|
|
14
|
+
checkGlobalDuplicate,
|
|
15
|
+
checkLocalBindings,
|
|
16
|
+
checkPattern,
|
|
17
|
+
xsltLocalName,
|
|
18
|
+
} from "../stylesheetChecks.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The properties of an xsl:decimal-format declaration, with their defaults
|
|
22
|
+
* (XSLT 1.0 section 12.3).
|
|
23
|
+
*
|
|
24
|
+
* @param {Element} node - The xsl:decimal-format element
|
|
25
|
+
* @param {number} importPrecedence - Its import precedence
|
|
26
|
+
* @returns {object} The decimal format
|
|
27
|
+
*/
|
|
28
|
+
function readDecimalFormat(node, importPrecedence) {
|
|
29
|
+
return {
|
|
30
|
+
importPrecedence,
|
|
31
|
+
decimalSeparator: node.getAttribute("decimal-separator") || ".",
|
|
32
|
+
groupingSeparator: node.getAttribute("grouping-separator") || ",",
|
|
33
|
+
percent: node.getAttribute("percent") || "%",
|
|
34
|
+
perMille: node.getAttribute("per-mille") || "‰",
|
|
35
|
+
zeroDigit: node.getAttribute("zero-digit") || "0",
|
|
36
|
+
digit: node.getAttribute("digit") || "#",
|
|
37
|
+
patternSeparator: node.getAttribute("pattern-separator") || ";",
|
|
38
|
+
infinity: node.getAttribute("infinity") || "Infinity",
|
|
39
|
+
nan: node.getAttribute("NaN") || "NaN",
|
|
40
|
+
minusSign: node.getAttribute("minus-sign") || "-",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const declarationMethods = {
|
|
45
|
+
/**
|
|
46
|
+
* Register an `xsl:variable` top level declaration.
|
|
47
|
+
*
|
|
48
|
+
* @param {Element} node - The `xsl:variable` element
|
|
49
|
+
* @returns {void}
|
|
50
|
+
*/
|
|
51
|
+
processGlobalVariable(node) {
|
|
52
|
+
const name = node.getAttribute("name");
|
|
53
|
+
this.checkGlobalBinding(name, node);
|
|
54
|
+
this.globalVariables[name] = {
|
|
55
|
+
node,
|
|
56
|
+
select: node.getAttribute("select"),
|
|
57
|
+
importPrecedence: this.currentImportPrecedence,
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Warn about a global variable or parameter declared twice at the same
|
|
63
|
+
* import precedence (an error in XSLT 1.0 section 11.4 that libxslt
|
|
64
|
+
* reports) and about duplicate local bindings in its content. The later
|
|
65
|
+
* declaration is used, or the xsl:variable when a parameter has the same
|
|
66
|
+
* name and import precedence.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} name - The declared name
|
|
69
|
+
* @param {Element} node - The xsl:variable or xsl:param element
|
|
70
|
+
* @returns {void}
|
|
71
|
+
*/
|
|
72
|
+
checkGlobalBinding(name, node) {
|
|
73
|
+
const warn = (message) => this.warnOnce(message);
|
|
74
|
+
checkLocalBindings(node, warn);
|
|
75
|
+
checkGlobalDuplicate(
|
|
76
|
+
{
|
|
77
|
+
name,
|
|
78
|
+
kind: xsltLocalName(node),
|
|
79
|
+
precedence: this.currentImportPrecedence,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
variable: this.globalVariables[name],
|
|
83
|
+
param: this.globalParameters[name],
|
|
84
|
+
},
|
|
85
|
+
warn,
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Register an `xsl:param` top level declaration.
|
|
91
|
+
*
|
|
92
|
+
* A value supplied from outside (through `setParameter`) has precedence over
|
|
93
|
+
* the declared default, so it survives compilation of the stylesheet.
|
|
94
|
+
*
|
|
95
|
+
* @param {Element} node - The `xsl:param` element
|
|
96
|
+
* @returns {void}
|
|
97
|
+
*/
|
|
98
|
+
processGlobalParam(node) {
|
|
99
|
+
const name = node.getAttribute("name");
|
|
100
|
+
this.checkGlobalBinding(name, node);
|
|
101
|
+
const existing = this.globalParameters[name];
|
|
102
|
+
const definition = {
|
|
103
|
+
node,
|
|
104
|
+
select: node.getAttribute("select"),
|
|
105
|
+
importPrecedence: this.currentImportPrecedence,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
if (existing && "value" in existing) {
|
|
109
|
+
definition.value = existing.value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.globalParameters[name] = definition;
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Register an xsl:key declaration. Declarations sharing a name all
|
|
117
|
+
* contribute to the same key (XSLT 1.0 section 12.2); a declaration seen
|
|
118
|
+
* again through another import branch is only kept once.
|
|
119
|
+
*
|
|
120
|
+
* @param {Element} node - The xsl:key element
|
|
121
|
+
* @returns {void}
|
|
122
|
+
*/
|
|
123
|
+
processKey(node) {
|
|
124
|
+
const name = node.getAttribute("name");
|
|
125
|
+
const match = node.getAttribute("match");
|
|
126
|
+
const use = node.getAttribute("use");
|
|
127
|
+
if (match !== null) {
|
|
128
|
+
checkPattern(this.patternMatcher, match, "xsl:key match");
|
|
129
|
+
}
|
|
130
|
+
const definitions = (this.keys[name] ??= []);
|
|
131
|
+
|
|
132
|
+
if (!definitions.some((d) => d.match === match && d.use === use)) {
|
|
133
|
+
definitions.push({ match, use, namespaces: inScopeNamespaces(node) });
|
|
134
|
+
}
|
|
135
|
+
this.keyRegistry.clear();
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Register an xsl:decimal-format declaration under the expanded name of
|
|
140
|
+
* its `name` (XSLT 1.0 section 12.3). Like libxslt, an invalid name or an
|
|
141
|
+
* undeclared prefix is reported and the declaration ignored, and a second
|
|
142
|
+
* declaration of the same name and import precedence is reported and
|
|
143
|
+
* ignored; one of a higher import precedence replaces an imported one.
|
|
144
|
+
*
|
|
145
|
+
* @param {Element} node - The xsl:decimal-format element
|
|
146
|
+
* @returns {void}
|
|
147
|
+
*/
|
|
148
|
+
processDecimalFormat(node) {
|
|
149
|
+
const name = node.getAttribute("name");
|
|
150
|
+
let key = "";
|
|
151
|
+
if (name !== null) {
|
|
152
|
+
const expanded = expandName(name, node);
|
|
153
|
+
if (expanded.error) {
|
|
154
|
+
this.warnOnce(
|
|
155
|
+
`xsl:decimal-format: ${expanded.error}; the declaration is ignored`,
|
|
156
|
+
);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
key = expanded.key;
|
|
160
|
+
const existing = this.decimalFormats[key];
|
|
161
|
+
if (existing?.importPrecedence === this.currentImportPrecedence) {
|
|
162
|
+
this.warnOnce(
|
|
163
|
+
`xsl:decimal-format: "${name}" is already declared; the first declaration is used`,
|
|
164
|
+
);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.decimalFormats[key] = readDecimalFormat(
|
|
170
|
+
node,
|
|
171
|
+
this.currentImportPrecedence,
|
|
172
|
+
);
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Register an xsl:namespace-alias declaration.
|
|
177
|
+
*
|
|
178
|
+
* @param {Element} node - The xsl:namespace-alias element
|
|
179
|
+
* @returns {void}
|
|
180
|
+
*/
|
|
181
|
+
processNamespaceAlias(node) {
|
|
182
|
+
this.namespaceAliases.add(node);
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Register an xsl:attribute-set declaration; declarations of the same
|
|
187
|
+
* expanded name are merged (see attributeSets.js).
|
|
188
|
+
*
|
|
189
|
+
* @param {Element} node - The xsl:attribute-set element
|
|
190
|
+
* @returns {void}
|
|
191
|
+
*/
|
|
192
|
+
processAttributeSet(node) {
|
|
193
|
+
checkLocalBindings(node, (message) => this.warnOnce(message));
|
|
194
|
+
registerAttributeSet(
|
|
195
|
+
this.attributeSets,
|
|
196
|
+
node,
|
|
197
|
+
this.currentImportPrecedence,
|
|
198
|
+
);
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Register the name tests of an xsl:strip-space element, expanded with
|
|
203
|
+
* its namespace declarations (see spaceNameTests.js).
|
|
204
|
+
*
|
|
205
|
+
* @param {Element} node - The xsl:strip-space element
|
|
206
|
+
* @returns {void}
|
|
207
|
+
*/
|
|
208
|
+
processStripSpace(node) {
|
|
209
|
+
this.stripSpace.push(...this.spaceNameTests(node));
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Register the name tests of an xsl:preserve-space element.
|
|
214
|
+
*
|
|
215
|
+
* @param {Element} node - The xsl:preserve-space element
|
|
216
|
+
* @returns {void}
|
|
217
|
+
*/
|
|
218
|
+
processPreserveSpace(node) {
|
|
219
|
+
this.preserveSpace.push(...this.spaceNameTests(node));
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Expand the `elements` name tests of xsl:strip-space/preserve-space.
|
|
224
|
+
*
|
|
225
|
+
* @param {Element} node - The declaring element
|
|
226
|
+
* @returns {object[]} The expanded name tests
|
|
227
|
+
*/
|
|
228
|
+
spaceNameTests(node) {
|
|
229
|
+
return compileSpaceNameTests(node.getAttribute("elements"), node, (m) =>
|
|
230
|
+
this.warnOnce(m),
|
|
231
|
+
);
|
|
232
|
+
},
|
|
233
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine services behind the XSLT functions: document(), generate-id()
|
|
3
|
+
* and key() (see functions.js).
|
|
4
|
+
*
|
|
5
|
+
* Methods installed on XsltEngine.prototype (`this` is the engine).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { resolveUri, stripFragment } from "../uri.js";
|
|
9
|
+
|
|
10
|
+
export const functionSupportMethods = {
|
|
11
|
+
/**
|
|
12
|
+
* Set the loader used by the XSLT `document()` function.
|
|
13
|
+
*
|
|
14
|
+
* The loader is synchronous and must return a `Document`, an XML string or
|
|
15
|
+
* null. Returning null (or configuring no loader at all) makes `document()`
|
|
16
|
+
* evaluate to an empty node-set instead of failing the transformation.
|
|
17
|
+
*
|
|
18
|
+
* @param {((uri: string, baseUri?: string) => (Document|string|null))|null} loader - The loader, or null to remove it
|
|
19
|
+
* @returns {XsltEngine} This engine, to allow chaining
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* engine.setDocumentLoader((uri) => readFileSync(uri, 'utf8'));
|
|
23
|
+
*/
|
|
24
|
+
setDocumentLoader(loader) {
|
|
25
|
+
this.documentLoader = loader ?? null;
|
|
26
|
+
this.loadedDocuments.clear();
|
|
27
|
+
return this;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Load an external document for the `document()` function.
|
|
32
|
+
*
|
|
33
|
+
* Results are cached per resolved URI for the life of the engine, so the same
|
|
34
|
+
* URI always yields the identical node-set.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} uri - The requested URI, fragment identifiers are ignored
|
|
37
|
+
* @param {string} [baseUri] - Base URI used to resolve relative references
|
|
38
|
+
* @returns {Document|null} The loaded document, or null when unavailable
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* engine.loadDocument('data.xml', '/styles/main.xsl');
|
|
42
|
+
*/
|
|
43
|
+
loadDocument(uri, baseUri) {
|
|
44
|
+
const target = stripFragment(uri);
|
|
45
|
+
|
|
46
|
+
if (target === "") return this.stylesheetDoc;
|
|
47
|
+
if (!this.documentLoader) return null;
|
|
48
|
+
|
|
49
|
+
const resolved = resolveUri(target, baseUri);
|
|
50
|
+
if (this.loadedDocuments.has(resolved)) {
|
|
51
|
+
return this.loadedDocuments.get(resolved);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const loaded = this.documentLoader(resolved, baseUri);
|
|
55
|
+
const doc =
|
|
56
|
+
typeof loaded === "string" ? this.parseXmlString(loaded) : loaded || null;
|
|
57
|
+
|
|
58
|
+
this.loadedDocuments.set(resolved, doc);
|
|
59
|
+
return doc;
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Return the stable identifier of a node for `generate-id()`.
|
|
64
|
+
*
|
|
65
|
+
* @param {Node} node - The node to identify
|
|
66
|
+
* @returns {string} An identifier starting with a letter
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* engine.generateId(element); // 'N1'
|
|
70
|
+
*/
|
|
71
|
+
generateId(node) {
|
|
72
|
+
let id = this.generatedIds.get(node);
|
|
73
|
+
if (!id) {
|
|
74
|
+
this.generatedIdCount++;
|
|
75
|
+
id = `N${this.generatedIdCount}`;
|
|
76
|
+
this.generatedIds.set(node, id);
|
|
77
|
+
}
|
|
78
|
+
return id;
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Evaluate the `use` expression of an `xsl:key` for one node.
|
|
83
|
+
*
|
|
84
|
+
* @param {Node} node - The node being indexed
|
|
85
|
+
* @param {string} expression - The `use` expression
|
|
86
|
+
* @param {Object<string, string>} [namespaces] - Prefixes in scope on the xsl:key
|
|
87
|
+
* @returns {string[]} The key values contributed by the node
|
|
88
|
+
*/
|
|
89
|
+
evaluateKeyValues(node, expression, namespaces) {
|
|
90
|
+
const context = this.rootContext.clone({
|
|
91
|
+
currentNode: node,
|
|
92
|
+
currentNodeList: [node],
|
|
93
|
+
position: 1,
|
|
94
|
+
});
|
|
95
|
+
if (namespaces) context.namespaces = namespaces;
|
|
96
|
+
const value = this.evaluateXPath(expression, context);
|
|
97
|
+
|
|
98
|
+
if (Array.isArray(value)) {
|
|
99
|
+
return value.map((item) => this.xpathEvaluator.getStringValue(item));
|
|
100
|
+
}
|
|
101
|
+
return [this.xpathEvaluator.toString(value)];
|
|
102
|
+
},
|
|
103
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installation of the XsltEngine method groups.
|
|
3
|
+
*
|
|
4
|
+
* The engine is split by concern into modules that each export a plain
|
|
5
|
+
* object of methods (`this` is the engine). They are installed on
|
|
6
|
+
* `XsltEngine.prototype`, so every method stays callable on the engine under
|
|
7
|
+
* its old name and the instruction dispatch (`this[method](...)`) costs no
|
|
8
|
+
* extra stack frame per recursion level.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Define the methods (and accessors) of each group on a prototype, as
|
|
13
|
+
* non-enumerable properties like class methods.
|
|
14
|
+
*
|
|
15
|
+
* @param {object} prototype - The class prototype receiving the methods
|
|
16
|
+
* @param {...object} groups - Objects of methods, getters included
|
|
17
|
+
* @returns {void}
|
|
18
|
+
* @throws {Error} When two groups define the same name
|
|
19
|
+
*/
|
|
20
|
+
export function installMethods(prototype, ...groups) {
|
|
21
|
+
for (const group of groups) {
|
|
22
|
+
const descriptors = Object.getOwnPropertyDescriptors(group);
|
|
23
|
+
for (const [name, descriptor] of Object.entries(descriptors)) {
|
|
24
|
+
if (Object.hasOwn(prototype, name)) {
|
|
25
|
+
throw new Error(`Engine method defined twice: ${name}`);
|
|
26
|
+
}
|
|
27
|
+
Object.defineProperty(prototype, name, {
|
|
28
|
+
...descriptor,
|
|
29
|
+
enumerable: false,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creation of result elements and attributes: literal result elements,
|
|
3
|
+
* xsl:element, xsl:attribute and attribute sets.
|
|
4
|
+
*
|
|
5
|
+
* Methods installed on XsltEngine.prototype (`this` is the engine).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { XSLT_NAMESPACE } from "../elements.js";
|
|
9
|
+
import { getXsltAttribute, shouldCopyAttribute } from "../literalResult.js";
|
|
10
|
+
import {
|
|
11
|
+
copyLiteralNamespaces,
|
|
12
|
+
setResultAttribute,
|
|
13
|
+
} from "../resultNamespaces.js";
|
|
14
|
+
import {
|
|
15
|
+
computedAttributeName,
|
|
16
|
+
computedElementName,
|
|
17
|
+
} from "../computedNames.js";
|
|
18
|
+
import { applyAttributeSets } from "../attributeSets.js";
|
|
19
|
+
import { requireExpandedNames } from "../declarationNames.js";
|
|
20
|
+
|
|
21
|
+
export const nodeConstructionMethods = {
|
|
22
|
+
/**
|
|
23
|
+
* Copy one attribute of a literal result element to the result, evaluating
|
|
24
|
+
* it as an attribute value template and applying xsl:namespace-alias.
|
|
25
|
+
*
|
|
26
|
+
* @param {Attr} attr - Attribute of the literal result element
|
|
27
|
+
* @param {XsltContext} context - The current XSLT context
|
|
28
|
+
* @param {Element} outputElement - The result element
|
|
29
|
+
* @returns {void}
|
|
30
|
+
*/
|
|
31
|
+
copyLiteralAttribute(attr, context, outputElement) {
|
|
32
|
+
const value = this.processAttributeValueTemplate(attr.value, context);
|
|
33
|
+
const alias = this.namespaceAliases.resolveLiteral(attr);
|
|
34
|
+
|
|
35
|
+
if (alias) {
|
|
36
|
+
outputElement.setAttributeNS(alias.namespaceUri, alias.qname, value);
|
|
37
|
+
} else if (attr.namespaceURI) {
|
|
38
|
+
outputElement.setAttributeNS(attr.namespaceURI, attr.name, value);
|
|
39
|
+
} else {
|
|
40
|
+
outputElement.setAttribute(attr.name, value);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Process a literal result element (non-XSLT)
|
|
46
|
+
*
|
|
47
|
+
* Applies `xsl:namespace-alias` to the element and its attributes, honours
|
|
48
|
+
* `xsl:use-attribute-sets` and keeps XSLT-only attributes and namespace
|
|
49
|
+
* declarations out of the result tree.
|
|
50
|
+
*
|
|
51
|
+
* @param {Element} node - The literal result element in the stylesheet
|
|
52
|
+
* @param {XsltContext} context - The current XSLT context
|
|
53
|
+
* @param {Node} output - The result tree node receiving the element
|
|
54
|
+
* @returns {void}
|
|
55
|
+
*/
|
|
56
|
+
processLiteralResultElement(node, context, output) {
|
|
57
|
+
const alias = this.namespaceAliases.resolveLiteral(node);
|
|
58
|
+
const namespaceUri = alias ? alias.namespaceUri : node.namespaceURI;
|
|
59
|
+
const qname = alias ? alias.qname : node.nodeName;
|
|
60
|
+
|
|
61
|
+
const outputElement =
|
|
62
|
+
namespaceUri && context.outputDocument.createElementNS
|
|
63
|
+
? context.outputDocument.createElementNS(namespaceUri, qname)
|
|
64
|
+
: context.outputDocument.createElement(qname);
|
|
65
|
+
// Attached first, so namespace lookups see the result ancestors
|
|
66
|
+
output.appendChild(outputElement);
|
|
67
|
+
copyLiteralNamespaces(outputElement, node, output, (uri) =>
|
|
68
|
+
this.namespaceAliases.isAliased(uri),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Attribute sets come first so literal attributes take precedence
|
|
72
|
+
const useAttributeSets = getXsltAttribute(
|
|
73
|
+
node,
|
|
74
|
+
"use-attribute-sets",
|
|
75
|
+
XSLT_NAMESPACE,
|
|
76
|
+
);
|
|
77
|
+
if (useAttributeSets) {
|
|
78
|
+
this.applyAttributeSets(useAttributeSets, context, outputElement, node);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const attr of node.attributes) {
|
|
82
|
+
if (shouldCopyAttribute(attr, XSLT_NAMESPACE)) {
|
|
83
|
+
this.copyLiteralAttribute(attr, context, outputElement);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.scheduleChildren(node, context, outputElement);
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Instantiate `xsl:element`. The name's prefix, or the default namespace
|
|
92
|
+
* for an unprefixed name, resolves against the namespaces in scope on the
|
|
93
|
+
* instruction; a `namespace` attribute wins (XSLT 1.0 section 7.1.2).
|
|
94
|
+
* An invalid name or an undeclared prefix is reported and, as in libxslt,
|
|
95
|
+
* neither the element nor its content is created.
|
|
96
|
+
*
|
|
97
|
+
* @param {Element} node - The xsl:element instruction
|
|
98
|
+
* @param {XsltContext} context - The current context
|
|
99
|
+
* @param {Node} output - The result node receiving the element
|
|
100
|
+
* @returns {void}
|
|
101
|
+
*/
|
|
102
|
+
xslElement(node, context, output) {
|
|
103
|
+
const { name, error } = computedElementName(
|
|
104
|
+
this.processAttributeValueTemplate(node.getAttribute("name"), context),
|
|
105
|
+
this.optionalAvt(node, "namespace", context),
|
|
106
|
+
context.namespaces,
|
|
107
|
+
);
|
|
108
|
+
if (error) {
|
|
109
|
+
this.warnOnce(error);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const { namespaceUri, qname } = name;
|
|
113
|
+
const element = namespaceUri
|
|
114
|
+
? context.outputDocument.createElementNS(namespaceUri, qname)
|
|
115
|
+
: context.outputDocument.createElement(qname);
|
|
116
|
+
output.appendChild(element);
|
|
117
|
+
|
|
118
|
+
const useAttributeSets = node.getAttribute("use-attribute-sets");
|
|
119
|
+
if (useAttributeSets) {
|
|
120
|
+
this.applyAttributeSets(useAttributeSets, context, element, node);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.scheduleChildren(node, context, element);
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Instantiate `xsl:attribute` (XSLT 1.0 section 7.1.3): a prefixed name
|
|
128
|
+
* uses the namespace bound in scope, `namespace` wins, and a namespaced
|
|
129
|
+
* attribute without a usable prefix gets a generated one. An invalid name,
|
|
130
|
+
* an undeclared prefix or `xmlns` is reported and the attribute skipped.
|
|
131
|
+
*
|
|
132
|
+
* @param {Element} node - The xsl:attribute instruction
|
|
133
|
+
* @param {XsltContext} context - The current context
|
|
134
|
+
* @param {Node} output - The result element receiving the attribute
|
|
135
|
+
* @returns {void}
|
|
136
|
+
*/
|
|
137
|
+
xslAttribute(node, context, output) {
|
|
138
|
+
if (!this.canAddAttribute(output)) return;
|
|
139
|
+
|
|
140
|
+
const { name, error } = computedAttributeName(
|
|
141
|
+
this.processAttributeValueTemplate(node.getAttribute("name"), context),
|
|
142
|
+
this.optionalAvt(node, "namespace", context),
|
|
143
|
+
context.namespaces,
|
|
144
|
+
);
|
|
145
|
+
if (error) {
|
|
146
|
+
this.warnOnce(error);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
setResultAttribute(output, name, this.instantiateText(node, context));
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Whether an attribute may still be added to a result node: it must be an
|
|
154
|
+
* element without children. Attributes added after children are ignored
|
|
155
|
+
* (as libxslt does), with a single warning per engine.
|
|
156
|
+
*
|
|
157
|
+
* @param {Node} target - The result node
|
|
158
|
+
* @returns {boolean} True when the attribute can be added
|
|
159
|
+
*/
|
|
160
|
+
canAddAttribute(target) {
|
|
161
|
+
if (target.nodeType !== 1) return false;
|
|
162
|
+
if (!target.firstChild) return true;
|
|
163
|
+
this.warnOnce(
|
|
164
|
+
"an attribute created after the children of an element is ignored (XSLT 1.0 section 7.1.3)",
|
|
165
|
+
);
|
|
166
|
+
return false;
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Apply the attribute sets named by a `use-attribute-sets` attribute (see
|
|
171
|
+
* attributeSets.js). The xsl:attribute children of each declaration are
|
|
172
|
+
* instantiated with the prefixes in scope there.
|
|
173
|
+
*
|
|
174
|
+
* @param {string} names - Whitespace separated QNames
|
|
175
|
+
* @param {XsltContext} context - Context of the instruction
|
|
176
|
+
* @param {Element} element - The result element receiving the attributes
|
|
177
|
+
* @param {Element} instruction - The element carrying the attribute, whose
|
|
178
|
+
* namespace declarations expand the names
|
|
179
|
+
* @returns {void}
|
|
180
|
+
*/
|
|
181
|
+
applyAttributeSets(names, context, element, instruction) {
|
|
182
|
+
const keys = requireExpandedNames(names, instruction, "use-attribute-sets");
|
|
183
|
+
applyAttributeSets(this.attributeSets, keys, (declaration) =>
|
|
184
|
+
this.processChildren(declaration, context, element),
|
|
185
|
+
);
|
|
186
|
+
},
|
|
187
|
+
};
|