@tradik/xslt-processor 1.0.2 → 1.1.1
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/README.md +292 -47
- package/bin/lib/options.js +114 -0
- package/bin/lib/paths.js +186 -0
- package/bin/lib/transform.js +115 -0
- package/bin/xslt.js +68 -162
- package/dist/xslt-processor.browser.js +2073 -163
- package/dist/xslt-processor.browser.js.map +4 -4
- package/dist/xslt-processor.browser.min.js +6 -2
- package/dist/xslt-processor.browser.min.js.map +4 -4
- package/dist/xslt-processor.cjs +2077 -162
- package/dist/xslt-processor.cjs.map +4 -4
- package/dist/xslt-processor.d.cts +299 -0
- package/dist/xslt-processor.d.ts +92 -4
- package/dist/xslt-processor.js +2072 -161
- package/dist/xslt-processor.js.map +4 -4
- package/package.json +27 -16
- package/src/XSLTProcessor.js +177 -8
- package/src/index.js +11 -5
- package/src/xpath/evaluator.js +48 -7
- package/src/xslt/elements.js +57 -0
- package/src/xslt/engine.js +474 -185
- package/src/xslt/formatNumber.js +220 -0
- package/src/xslt/functions.js +191 -0
- package/src/xslt/index.js +31 -0
- package/src/xslt/keys.js +141 -0
- package/src/xslt/literalResult.js +167 -0
- package/src/xslt/number.js +178 -0
- package/src/xslt/numberFormat.js +155 -0
- package/src/xslt/resultTree.js +74 -0
- package/src/xslt/serializer/baseWriter.js +283 -0
- package/src/xslt/serializer/constants.js +78 -0
- package/src/xslt/serializer/escape.js +98 -0
- package/src/xslt/serializer/htmlSerializer.js +141 -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 +103 -0
- package/src/xslt/serializer/textSerializer.js +29 -0
- package/src/xslt/serializer/xmlSerializer.js +127 -0
- package/src/xslt/serializer.js +57 -0
- package/src/xslt/templatePriority.js +45 -0
- package/src/xslt/uri.js +68 -0
- package/src/xslt/whitespace.js +184 -0
- 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,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Namespace Declaration Tracking
|
|
3
|
+
*
|
|
4
|
+
* Computes the `xmlns` declarations an element has to carry so that every
|
|
5
|
+
* namespace is declared where it is first used and never twice.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { XMLNS_NAMESPACE, XML_NAMESPACE } from "./constants.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create the namespace scope in effect above the result tree root.
|
|
12
|
+
*
|
|
13
|
+
* @returns {Map<string, string>} Prefix (empty string for the default) to URI
|
|
14
|
+
*/
|
|
15
|
+
export function createNamespaceScope() {
|
|
16
|
+
return new Map([
|
|
17
|
+
["", ""],
|
|
18
|
+
["xml", XML_NAMESPACE],
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Collect the namespace declarations an element must emit.
|
|
24
|
+
*
|
|
25
|
+
* @param {Element} element - Element being serialized
|
|
26
|
+
* @param {Map<string, string>} scope - Namespace scope inherited from the parent
|
|
27
|
+
* @returns {{declarations: Array<{prefix: string, uri: string}>, scope: Map<string, string>}}
|
|
28
|
+
* The declarations to write and the scope in effect for the children
|
|
29
|
+
*/
|
|
30
|
+
export function collectNamespaceDeclarations(element, scope) {
|
|
31
|
+
const declarations = [];
|
|
32
|
+
let next = scope;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Record a declaration when the binding is not already in scope.
|
|
36
|
+
* @param {string} prefix - Namespace prefix, empty for the default namespace
|
|
37
|
+
* @param {string} uri - Namespace URI
|
|
38
|
+
* @returns {void}
|
|
39
|
+
*/
|
|
40
|
+
const declare = (prefix, uri) => {
|
|
41
|
+
if (next.get(prefix) === uri) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (next === scope) {
|
|
45
|
+
next = new Map(scope);
|
|
46
|
+
}
|
|
47
|
+
next.set(prefix, uri);
|
|
48
|
+
declarations.push({ prefix, uri });
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
declare(element.prefix || "", element.namespaceURI || "");
|
|
52
|
+
|
|
53
|
+
const attributes = Array.from(element.attributes || []);
|
|
54
|
+
|
|
55
|
+
for (const attribute of attributes) {
|
|
56
|
+
if (attribute.namespaceURI !== XMLNS_NAMESPACE && attribute.prefix) {
|
|
57
|
+
declare(attribute.prefix, attribute.namespaceURI || "");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const attribute of attributes) {
|
|
62
|
+
if (attribute.namespaceURI === XMLNS_NAMESPACE) {
|
|
63
|
+
declare(attribute.prefix ? attribute.localName : "", attribute.value);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { declarations, scope: next };
|
|
68
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw Text Registry
|
|
3
|
+
*
|
|
4
|
+
* Tracks the text nodes produced with `disable-output-escaping="yes"`
|
|
5
|
+
* (XSLT 1.0 section 16.4) so the serializers can emit them verbatim.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Text nodes whose content must be written without escaping.
|
|
10
|
+
* @type {WeakSet<Node>}
|
|
11
|
+
*/
|
|
12
|
+
export const rawTextNodes = new WeakSet();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Mark a text node as produced with `disable-output-escaping="yes"`.
|
|
16
|
+
*
|
|
17
|
+
* @param {Node|null} node - Text node to mark
|
|
18
|
+
* @returns {Node|null} The same node, for chaining
|
|
19
|
+
*/
|
|
20
|
+
export function markRawText(node) {
|
|
21
|
+
if (node) {
|
|
22
|
+
rawTextNodes.add(node);
|
|
23
|
+
}
|
|
24
|
+
return node;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check whether a node must be serialized without output escaping.
|
|
29
|
+
*
|
|
30
|
+
* Both the registry and the legacy `_disableOutputEscaping` flag set by the
|
|
31
|
+
* XSLT engine are honored so that either marking mechanism works.
|
|
32
|
+
*
|
|
33
|
+
* @param {Node|null} node - Node to test
|
|
34
|
+
* @returns {boolean} True when the node content must be emitted raw
|
|
35
|
+
*/
|
|
36
|
+
export function isRawText(node) {
|
|
37
|
+
if (!node) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return rawTextNodes.has(node) || node._disableOutputEscaping === true;
|
|
41
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output Settings Resolution
|
|
3
|
+
*
|
|
4
|
+
* Normalizes the `xsl:output` settings collected by the XSLT engine into the
|
|
5
|
+
* shape the serializers consume (XSLT 1.0 section 16).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { NODE_TYPE } from "./constants.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Test whether an `xsl:output` yes/no attribute is enabled.
|
|
12
|
+
*
|
|
13
|
+
* @param {string|boolean|undefined} value - Raw attribute value
|
|
14
|
+
* @returns {boolean} True when the value means "yes"
|
|
15
|
+
*/
|
|
16
|
+
function isYes(value) {
|
|
17
|
+
return value === true || String(value).toLowerCase() === "yes";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Convert a `cdata-section-elements` value into a lookup set.
|
|
22
|
+
*
|
|
23
|
+
* @param {string|string[]|undefined} value - Whitespace separated names or array
|
|
24
|
+
* @returns {Set<string>} Element names requiring CDATA sections
|
|
25
|
+
*/
|
|
26
|
+
function toNameSet(value) {
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return new Set(value);
|
|
29
|
+
}
|
|
30
|
+
if (typeof value === "string") {
|
|
31
|
+
return new Set(value.split(/\s+/).filter(Boolean));
|
|
32
|
+
}
|
|
33
|
+
return new Set();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Find the first element node of a result tree.
|
|
38
|
+
*
|
|
39
|
+
* @param {Node|null} node - Document, fragment or element
|
|
40
|
+
* @returns {Element|null} The result document element, when there is one
|
|
41
|
+
*/
|
|
42
|
+
export function findRootElement(node) {
|
|
43
|
+
if (!node) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (node.nodeType === NODE_TYPE.ELEMENT) {
|
|
47
|
+
return node;
|
|
48
|
+
}
|
|
49
|
+
for (const child of node.childNodes || []) {
|
|
50
|
+
if (child.nodeType === NODE_TYPE.ELEMENT) {
|
|
51
|
+
return child;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Derive the default output method from the result tree.
|
|
59
|
+
*
|
|
60
|
+
* XSLT 1.0 section 16 defaults to `html` when the document element is `html`
|
|
61
|
+
* in no namespace, and to `xml` otherwise.
|
|
62
|
+
*
|
|
63
|
+
* @param {Node|null} node - Result tree root
|
|
64
|
+
* @returns {string} Either "html" or "xml"
|
|
65
|
+
*/
|
|
66
|
+
export function detectOutputMethod(node) {
|
|
67
|
+
const root = findRootElement(node);
|
|
68
|
+
const isHtmlRoot =
|
|
69
|
+
root && !root.namespaceURI && root.localName.toLowerCase() === "html";
|
|
70
|
+
return isHtmlRoot ? "html" : "xml";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Normalize an `xsl:output` settings object.
|
|
75
|
+
*
|
|
76
|
+
* An absent, empty or "auto" method triggers the XSLT 1.0 default method
|
|
77
|
+
* detection based on the result tree.
|
|
78
|
+
*
|
|
79
|
+
* @param {object|null} outputSettings - Raw settings from the XSLT engine
|
|
80
|
+
* @param {Node|null} node - Result tree used for default method detection
|
|
81
|
+
* @returns {object} Normalized settings consumed by the serializers
|
|
82
|
+
*/
|
|
83
|
+
export function resolveOutputSettings(outputSettings, node) {
|
|
84
|
+
const raw = outputSettings || {};
|
|
85
|
+
const declared = typeof raw.method === "string" ? raw.method.trim() : "";
|
|
86
|
+
const method =
|
|
87
|
+
declared && declared !== "auto"
|
|
88
|
+
? declared.toLowerCase()
|
|
89
|
+
: detectOutputMethod(node);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
method,
|
|
93
|
+
version: raw.version || "1.0",
|
|
94
|
+
encoding: raw.encoding || "UTF-8",
|
|
95
|
+
standalone: raw.standalone || null,
|
|
96
|
+
indent: isYes(raw.indent),
|
|
97
|
+
omitXmlDeclaration: isYes(raw.omitXmlDeclaration),
|
|
98
|
+
doctypePublic: raw.doctypePublic || null,
|
|
99
|
+
doctypeSystem: raw.doctypeSystem || null,
|
|
100
|
+
mediaType: raw.mediaType || null,
|
|
101
|
+
cdataSectionElements: toNameSet(raw.cdataSectionElements),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text Output Serializer
|
|
3
|
+
*
|
|
4
|
+
* Implements the `text` output method of XSLT 1.0 section 16.3: the result is
|
|
5
|
+
* the concatenation of every descendant character data node, unescaped.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { NODE_TYPE } from "./constants.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Serialize a result tree with the text output method.
|
|
12
|
+
*
|
|
13
|
+
* @param {Node} node - Document, fragment, element or character data node
|
|
14
|
+
* @returns {string} Concatenated character data
|
|
15
|
+
*/
|
|
16
|
+
export function serializeText(node) {
|
|
17
|
+
if (
|
|
18
|
+
node.nodeType === NODE_TYPE.TEXT ||
|
|
19
|
+
node.nodeType === NODE_TYPE.CDATA_SECTION
|
|
20
|
+
) {
|
|
21
|
+
return node.nodeValue || "";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let text = "";
|
|
25
|
+
for (const child of node.childNodes || []) {
|
|
26
|
+
text += serializeText(child);
|
|
27
|
+
}
|
|
28
|
+
return text;
|
|
29
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML Output Serializer
|
|
3
|
+
*
|
|
4
|
+
* Implements the `xml` output method of XSLT 1.0 section 16.1 and, with the
|
|
5
|
+
* `xhtml` option, the XHTML empty element convention. It is also the base of
|
|
6
|
+
* the html serializer, which overrides the dialect hooks defined here.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { TEXT_MODE, VOID_ELEMENTS } from "./constants.js";
|
|
10
|
+
import { escapeXmlAttribute, escapeXmlText } from "./escape.js";
|
|
11
|
+
import { BaseWriter } from "./baseWriter.js";
|
|
12
|
+
|
|
13
|
+
export class XmlWriter extends BaseWriter {
|
|
14
|
+
/**
|
|
15
|
+
* Whether an XML declaration has to be written.
|
|
16
|
+
* @returns {boolean} True when the declaration is not omitted
|
|
17
|
+
*/
|
|
18
|
+
get emitsXmlDeclaration() {
|
|
19
|
+
return !this.settings.omitXmlDeclaration;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Whether namespace declarations have to be written.
|
|
24
|
+
* @returns {boolean} Always true for XML output
|
|
25
|
+
*/
|
|
26
|
+
get emitsNamespaces() {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Terminator of a processing instruction.
|
|
32
|
+
* @returns {string} The XML processing instruction terminator
|
|
33
|
+
*/
|
|
34
|
+
get piTerminator() {
|
|
35
|
+
return "?>";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* How a source CDATA section node has to be written.
|
|
40
|
+
* @returns {string} A {@link TEXT_MODE} value
|
|
41
|
+
*/
|
|
42
|
+
get cdataNodeMode() {
|
|
43
|
+
return TEXT_MODE.CDATA;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Build the document type declaration for the xml output method.
|
|
48
|
+
*
|
|
49
|
+
* @param {Element|null} rootElement - Result document element
|
|
50
|
+
* @returns {string} Doctype markup, or an empty string when not applicable
|
|
51
|
+
*/
|
|
52
|
+
doctypeMarkup(rootElement) {
|
|
53
|
+
const { doctypePublic, doctypeSystem } = this.settings;
|
|
54
|
+
if (!rootElement || !doctypeSystem) {
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const name = rootElement.nodeName;
|
|
59
|
+
return doctypePublic
|
|
60
|
+
? `<!DOCTYPE ${name} PUBLIC "${doctypePublic}" "${doctypeSystem}">`
|
|
61
|
+
: `<!DOCTYPE ${name} SYSTEM "${doctypeSystem}">`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Determine how the character data children of an element are written.
|
|
66
|
+
*
|
|
67
|
+
* @param {Element} element - Parent element
|
|
68
|
+
* @returns {string} A {@link TEXT_MODE} value
|
|
69
|
+
*/
|
|
70
|
+
childTextMode(element) {
|
|
71
|
+
const names = this.settings.cdataSectionElements;
|
|
72
|
+
return names.has(element.nodeName) || names.has(element.localName)
|
|
73
|
+
? TEXT_MODE.CDATA
|
|
74
|
+
: TEXT_MODE.ESCAPE;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Whether the content of an element may be re-indented.
|
|
79
|
+
*
|
|
80
|
+
* @param {Element} _element - Element being inspected
|
|
81
|
+
* @returns {boolean} Always true for XML output
|
|
82
|
+
*/
|
|
83
|
+
allowsIndentInside(_element) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Build the markup closing an element that has no children.
|
|
89
|
+
*
|
|
90
|
+
* @param {Element} element - Empty element
|
|
91
|
+
* @param {string} _name - Element name as written
|
|
92
|
+
* @returns {string} Markup terminating the start tag
|
|
93
|
+
*/
|
|
94
|
+
emptyElementMarkup(element, _name) {
|
|
95
|
+
return this.xhtml && this.isVoidElement(element) ? " />" : "/>";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Test whether an element is an HTML void element.
|
|
100
|
+
*
|
|
101
|
+
* @param {Element} element - Element to test
|
|
102
|
+
* @returns {boolean} True for void elements such as `br`
|
|
103
|
+
*/
|
|
104
|
+
isVoidElement(element) {
|
|
105
|
+
return VOID_ELEMENTS.has(String(element.localName).toLowerCase());
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Escape character data.
|
|
110
|
+
*
|
|
111
|
+
* @param {string} value - Text content
|
|
112
|
+
* @returns {string} Escaped text
|
|
113
|
+
*/
|
|
114
|
+
escapeText(value) {
|
|
115
|
+
return escapeXmlText(value);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Escape an attribute value.
|
|
120
|
+
*
|
|
121
|
+
* @param {string} value - Attribute value
|
|
122
|
+
* @returns {string} Escaped value
|
|
123
|
+
*/
|
|
124
|
+
escapeAttribute(value) {
|
|
125
|
+
return escapeXmlAttribute(value);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XSLT Output Serializer
|
|
3
|
+
*
|
|
4
|
+
* Serializes a result tree to a string honoring the `xsl:output` settings of
|
|
5
|
+
* the stylesheet (XSLT 1.0 section 16): output method, indentation, XML
|
|
6
|
+
* declaration, document type declaration, CDATA sections and
|
|
7
|
+
* `disable-output-escaping`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* import { serializeResult } from './serializer.js';
|
|
11
|
+
*
|
|
12
|
+
* serializeResult(resultDocument, { method: 'xml', indent: 'yes' });
|
|
13
|
+
* // '<?xml version="1.0" encoding="UTF-8"?>\n<BAR>\n <QUX/>\n</BAR>'
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { resolveOutputSettings } from "./serializer/settings.js";
|
|
17
|
+
import { XmlWriter } from "./serializer/xmlSerializer.js";
|
|
18
|
+
import { HtmlWriter } from "./serializer/htmlSerializer.js";
|
|
19
|
+
import { serializeText } from "./serializer/textSerializer.js";
|
|
20
|
+
|
|
21
|
+
export { markRawText, isRawText, rawTextNodes } from "./serializer/rawText.js";
|
|
22
|
+
export {
|
|
23
|
+
resolveOutputSettings,
|
|
24
|
+
detectOutputMethod,
|
|
25
|
+
findRootElement,
|
|
26
|
+
} from "./serializer/settings.js";
|
|
27
|
+
export { XmlWriter } from "./serializer/xmlSerializer.js";
|
|
28
|
+
export { HtmlWriter } from "./serializer/htmlSerializer.js";
|
|
29
|
+
export { serializeText } from "./serializer/textSerializer.js";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Serialize a transformation result to a string.
|
|
33
|
+
*
|
|
34
|
+
* @param {Node|null} node - Result document, fragment or element
|
|
35
|
+
* @param {object} [outputSettings] - `xsl:output` settings, as collected by the
|
|
36
|
+
* XSLT engine (`method`, `version`, `encoding`, `standalone`, `indent`,
|
|
37
|
+
* `omitXmlDeclaration`, `doctypePublic`, `doctypeSystem`, `mediaType`,
|
|
38
|
+
* `cdataSectionElements`)
|
|
39
|
+
* @returns {string} The serialized result, or an empty string for a null node
|
|
40
|
+
*/
|
|
41
|
+
export function serializeResult(node, outputSettings = {}) {
|
|
42
|
+
if (!node) {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const settings = resolveOutputSettings(outputSettings, node);
|
|
47
|
+
|
|
48
|
+
if (settings.method === "text") {
|
|
49
|
+
return serializeText(node);
|
|
50
|
+
}
|
|
51
|
+
if (settings.method === "html") {
|
|
52
|
+
return new HtmlWriter(settings).serialize(node);
|
|
53
|
+
}
|
|
54
|
+
return new XmlWriter(settings, {
|
|
55
|
+
xhtml: settings.method === "xhtml",
|
|
56
|
+
}).serialize(node);
|
|
57
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default template priorities (XSLT 1.0 section 5.5).
|
|
3
|
+
*
|
|
4
|
+
* A template rule without an explicit `priority` attribute gets a default
|
|
5
|
+
* priority derived from the shape of its match pattern. A union pattern is
|
|
6
|
+
* treated as a set of template rules, one per alternative, so each
|
|
7
|
+
* alternative must be assigned its own priority by the caller.
|
|
8
|
+
*
|
|
9
|
+
* @module xslt/templatePriority
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const NAME = String.raw`[A-Za-z_][\w.-]*`;
|
|
13
|
+
const QNAME = `(?:${NAME}:)?${NAME}`;
|
|
14
|
+
|
|
15
|
+
/** Patterns of the form `name`, `prefix:name`, `@name`, `@prefix:name`. */
|
|
16
|
+
const QNAME_PATTERN = new RegExp(`^(?:child::|attribute::|@)?${QNAME}$`);
|
|
17
|
+
|
|
18
|
+
/** Patterns of the form `prefix:*` or `@prefix:*`. */
|
|
19
|
+
const PREFIX_WILDCARD_PATTERN = new RegExp(
|
|
20
|
+
String.raw`^(?:child::|attribute::|@)?${NAME}:\*$`,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/** Patterns of the form `*`, `@*`, `node()`, `text()`, `comment()`, `processing-instruction()`. */
|
|
24
|
+
const NODE_TEST_PATTERN =
|
|
25
|
+
/^(?:child::|attribute::|@)?(?:\*|node\(\)|text\(\)|comment\(\)|processing-instruction\(\))$/;
|
|
26
|
+
|
|
27
|
+
/** `processing-instruction('literal')` patterns. */
|
|
28
|
+
const PI_LITERAL_PATTERN =
|
|
29
|
+
/^(?:child::)?processing-instruction\(\s*(?:"[^"]*"|'[^']*')\s*\)$/;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Compute the default priority of a single (non-union) match pattern.
|
|
33
|
+
*
|
|
34
|
+
* @param {string|null|undefined} pattern - The match pattern, already trimmed
|
|
35
|
+
* @returns {number} -0.5, -0.25, 0 or 0.5 as defined by the specification
|
|
36
|
+
*/
|
|
37
|
+
export function calculatePriority(pattern) {
|
|
38
|
+
if (!pattern) return 0.5;
|
|
39
|
+
|
|
40
|
+
if (NODE_TEST_PATTERN.test(pattern)) return -0.5;
|
|
41
|
+
if (PREFIX_WILDCARD_PATTERN.test(pattern)) return -0.25;
|
|
42
|
+
if (QNAME_PATTERN.test(pattern) || PI_LITERAL_PATTERN.test(pattern)) return 0;
|
|
43
|
+
|
|
44
|
+
return 0.5;
|
|
45
|
+
}
|
package/src/xslt/uri.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URI helpers for XSLT stylesheet and document resolution.
|
|
3
|
+
*
|
|
4
|
+
* Kept deliberately small and dependency free: the engine only needs enough
|
|
5
|
+
* URI arithmetic to turn a relative `href` into something a host supplied
|
|
6
|
+
* loader can resolve, plus fragment removal for the `document()` function.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
/** Matches an absolute URI such as `http://`, `https://` or `file://`. */
|
|
12
|
+
const ABSOLUTE_URI_PATTERN = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolve a possibly relative URI against a base URI.
|
|
16
|
+
*
|
|
17
|
+
* Absolute URIs (with a scheme) and root relative URIs (starting with `/`)
|
|
18
|
+
* are returned untouched, as is any URI when no base is available.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} href - The URI to resolve
|
|
21
|
+
* @param {string} [baseUri] - The base URI, typically the stylesheet location
|
|
22
|
+
* @returns {string} The resolved URI
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* resolveUri('common.xsl', '/styles/main.xsl'); // '/styles/common.xsl'
|
|
26
|
+
*/
|
|
27
|
+
export function resolveUri(href, baseUri) {
|
|
28
|
+
if (!href) return href;
|
|
29
|
+
if (!baseUri || isAbsoluteUri(href) || href.startsWith("/")) {
|
|
30
|
+
return href;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const lastSlash = baseUri.lastIndexOf("/");
|
|
34
|
+
const baseDir = lastSlash >= 0 ? baseUri.substring(0, lastSlash + 1) : "";
|
|
35
|
+
|
|
36
|
+
return baseDir + href;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check whether a URI is absolute (has a scheme).
|
|
41
|
+
*
|
|
42
|
+
* @param {string} uri - The URI to inspect
|
|
43
|
+
* @returns {boolean} True when the URI carries a scheme
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* isAbsoluteUri('https://example.com/a.xml'); // true
|
|
47
|
+
*/
|
|
48
|
+
export function isAbsoluteUri(uri) {
|
|
49
|
+
return ABSOLUTE_URI_PATTERN.test(uri);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Remove a fragment identifier from a URI.
|
|
54
|
+
*
|
|
55
|
+
* XSLT 1.0 leaves the meaning of fragment identifiers passed to `document()`
|
|
56
|
+
* implementation defined; this processor ignores them.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} uri - The URI, possibly carrying a `#fragment`
|
|
59
|
+
* @returns {string} The URI without its fragment
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* stripFragment('data.xml#section'); // 'data.xml'
|
|
63
|
+
*/
|
|
64
|
+
export function stripFragment(uri) {
|
|
65
|
+
if (typeof uri !== "string") return "";
|
|
66
|
+
const hash = uri.indexOf("#");
|
|
67
|
+
return hash === -1 ? uri : uri.substring(0, hash);
|
|
68
|
+
}
|