@tradik/xslt-processor 1.1.1 → 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 +102 -757
- 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 +17 -0
- package/bin/lib/output.js +114 -0
- package/bin/lib/paths.js +3 -3
- package/bin/lib/transform.js +124 -33
- package/bin/xslt.js +26 -27
- package/dist/xslt-processor.browser.js +8784 -2720
- package/dist/xslt-processor.browser.js.map +4 -4
- package/dist/xslt-processor.browser.min.js +13 -6
- package/dist/xslt-processor.browser.min.js.map +4 -4
- package/dist/xslt-processor.cjs +8789 -2723
- package/dist/xslt-processor.cjs.map +4 -4
- package/dist/xslt-processor.d.cts +380 -21
- package/dist/xslt-processor.d.ts +380 -21
- package/dist/xslt-processor.js +8770 -2722
- package/dist/xslt-processor.js.map +4 -4
- package/package.json +51 -11
- package/src/XSLTProcessor.js +343 -66
- 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 +16 -4
- 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 +475 -355
- 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 +1 -1
- 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 +176 -2020
- 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 +22 -9
- package/src/xslt/forwardsCompatible.js +75 -0
- package/src/xslt/functions.js +94 -15
- package/src/xslt/index.js +7 -1
- package/src/xslt/keys.js +51 -28
- package/src/xslt/literalResult.js +63 -7
- package/src/xslt/matchScope.js +116 -0
- package/src/xslt/number.js +171 -78
- package/src/xslt/numberFormat.js +124 -26
- 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 +143 -6
- package/src/xslt/serializer/baseWriter.js +173 -66
- package/src/xslt/serializer/chunks.js +120 -0
- package/src/xslt/serializer/constants.js +14 -0
- package/src/xslt/serializer/encoding.js +327 -0
- package/src/xslt/serializer/escape.js +49 -12
- 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 +123 -25
- package/src/xslt/serializer/settings.js +89 -13
- package/src/xslt/serializer/textSerializer.js +58 -10
- package/src/xslt/serializer/xhtmlDocument.js +103 -0
- package/src/xslt/serializer/xmlSerializer.js +113 -13
- package/src/xslt/serializer.js +50 -17
- 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/variables.js +152 -0
- package/src/xslt/whitespace.js +43 -27
- package/LICENSE +0 -29
package/src/xslt/functions.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* XSLT-defined XPath functions.
|
|
3
3
|
*
|
|
4
|
-
* XSLT 1.0 section 12 adds functions to the XPath function library
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* XSLT 1.0 section 12 adds functions to the XPath function library, and the
|
|
5
|
+
* EXSLT `node-set()` extension (also under the msxsl namespace) is registered
|
|
6
|
+
* by expanded name next to them, together with the other EXSLT functions of
|
|
7
|
+
* `./exslt/index.js`. They live here rather than in `src/xpath` so
|
|
8
|
+
* that module stays a pure XPath 1.0 implementation; the engine registers this
|
|
9
|
+
* map on its evaluator through
|
|
7
10
|
* {@link XPathEvaluator#registerFunctions}.
|
|
8
11
|
*/
|
|
9
12
|
|
|
@@ -11,6 +14,18 @@
|
|
|
11
14
|
|
|
12
15
|
import { formatNumber, DEFAULT_DECIMAL_FORMAT } from "./formatNumber.js";
|
|
13
16
|
import { isXsltElementAvailable, XSLT_NAMESPACE } from "./elements.js";
|
|
17
|
+
import {
|
|
18
|
+
expandedFunctionName,
|
|
19
|
+
resolveNamespacePrefix,
|
|
20
|
+
} from "../xpath/evaluator.js";
|
|
21
|
+
import { rootNodeOf } from "../xpath/axes.js";
|
|
22
|
+
import { EXSLT_COMMON, createExsltFunctions } from "./exslt/index.js";
|
|
23
|
+
|
|
24
|
+
/** Namespace of the EXSLT common module (`exsl:node-set()`). */
|
|
25
|
+
export const EXSLT_COMMON_NAMESPACE = EXSLT_COMMON;
|
|
26
|
+
|
|
27
|
+
/** Namespace of the MSXML extension functions (`msxsl:node-set()`). */
|
|
28
|
+
export const MSXSL_NAMESPACE = "urn:schemas-microsoft-com:xslt";
|
|
14
29
|
|
|
15
30
|
/** Vendor identification reported by `system-property()`. */
|
|
16
31
|
export const VENDOR = "@tradik/xslt-processor";
|
|
@@ -72,6 +87,29 @@ function splitQName(qname) {
|
|
|
72
87
|
};
|
|
73
88
|
}
|
|
74
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Expanded name key of the decimal format named by the third argument of
|
|
92
|
+
* `format-number()`, with the prefix resolved in the expression's scope
|
|
93
|
+
* (XSLT 1.0 section 12.3); the table is keyed the same way (see
|
|
94
|
+
* declarationNames.js).
|
|
95
|
+
*
|
|
96
|
+
* @param {string} qname - The decimal format QName
|
|
97
|
+
* @param {{namespaces: Object<string, string>}} ctx - The XPath context
|
|
98
|
+
* @returns {string} `{uri}local`, or the local name in no namespace
|
|
99
|
+
* @throws {Error} When the prefix is not declared
|
|
100
|
+
*/
|
|
101
|
+
function decimalFormatKey(qname, ctx) {
|
|
102
|
+
const { prefix, localName } = splitQName(qname);
|
|
103
|
+
if (prefix === null) return localName;
|
|
104
|
+
const namespaceUri = resolveNamespacePrefix(prefix, ctx.namespaces);
|
|
105
|
+
if (!namespaceUri) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`format-number(): undeclared namespace prefix "${prefix}" in "${qname}"`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return `{${namespaceUri}}${localName}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
75
113
|
/**
|
|
76
114
|
* Build the XSLT function map for an engine.
|
|
77
115
|
*
|
|
@@ -96,7 +134,30 @@ export function createXsltFunctions(engine) {
|
|
|
96
134
|
const evaluate = (arg, ctx) => evaluator.evaluate(arg, ctx);
|
|
97
135
|
const asString = (arg, ctx) => stringify(evaluate(arg, ctx));
|
|
98
136
|
|
|
137
|
+
/**
|
|
138
|
+
* `exsl:node-set(object)` - EXSLT common, also known as `msxsl:node-set()`.
|
|
139
|
+
*
|
|
140
|
+
* A result tree fragment (a DocumentFragment in this engine) becomes a
|
|
141
|
+
* node-set holding its root node, so `exsl:node-set($rtf)/item` selects the
|
|
142
|
+
* fragment's top-level `item` elements. A node-set is returned unchanged; any
|
|
143
|
+
* other value becomes a node-set holding one text node with its string value.
|
|
144
|
+
*
|
|
145
|
+
* @param {Array} args - Argument expressions
|
|
146
|
+
* @param {import('../xpath/evaluator.js').XPathContext} ctx - Evaluation context
|
|
147
|
+
* @returns {Node[]} The node-set
|
|
148
|
+
*/
|
|
149
|
+
const nodeSet = (args, ctx) => {
|
|
150
|
+
const value = evaluate(args[0], ctx);
|
|
151
|
+
if (Array.isArray(value)) return value;
|
|
152
|
+
if (value?.nodeType) return [value];
|
|
153
|
+
return [ownerDocumentOf(ctx.node).createTextNode(stringify(value))];
|
|
154
|
+
};
|
|
155
|
+
|
|
99
156
|
return {
|
|
157
|
+
...createExsltFunctions(engine),
|
|
158
|
+
[expandedFunctionName(EXSLT_COMMON_NAMESPACE, "node-set")]: nodeSet,
|
|
159
|
+
[expandedFunctionName(MSXSL_NAMESPACE, "node-set")]: nodeSet,
|
|
160
|
+
|
|
100
161
|
/**
|
|
101
162
|
* `document(object, base?)` - load external XML documents.
|
|
102
163
|
*
|
|
@@ -117,18 +178,23 @@ export function createXsltFunctions(engine) {
|
|
|
117
178
|
return result;
|
|
118
179
|
},
|
|
119
180
|
|
|
120
|
-
/**
|
|
181
|
+
/**
|
|
182
|
+
* `key(name, value)` - look up nodes through an `xsl:key` index of the
|
|
183
|
+
* tree containing the context node: its document, or the fragment of a
|
|
184
|
+
* result tree fragment converted with `exsl:node-set()`.
|
|
185
|
+
*/
|
|
121
186
|
key: (args, ctx) => {
|
|
122
187
|
const name = asString(args[0], ctx);
|
|
123
188
|
const values = toStringList(evaluator, stringify, evaluate(args[1], ctx));
|
|
124
|
-
return engine.keyRegistry.lookup(name, values,
|
|
189
|
+
return engine.keyRegistry.lookup(name, values, rootNodeOf(ctx.node));
|
|
125
190
|
},
|
|
126
191
|
|
|
127
192
|
/** `format-number(number, pattern, decimalFormat?)`. */
|
|
128
193
|
"format-number": (args, ctx) => {
|
|
129
194
|
const value = evaluator.toNumber(evaluate(args[0], ctx));
|
|
130
195
|
const pattern = asString(args[1], ctx);
|
|
131
|
-
const formatName =
|
|
196
|
+
const formatName =
|
|
197
|
+
args.length > 2 ? decimalFormatKey(asString(args[2], ctx), ctx) : "";
|
|
132
198
|
const format =
|
|
133
199
|
engine.decimalFormats[formatName] || DEFAULT_DECIMAL_FORMAT;
|
|
134
200
|
return formatNumber(value, pattern, format);
|
|
@@ -160,22 +226,35 @@ export function createXsltFunctions(engine) {
|
|
|
160
226
|
: "";
|
|
161
227
|
},
|
|
162
228
|
|
|
163
|
-
/**
|
|
229
|
+
/**
|
|
230
|
+
* `function-available(name)` - reflects the evaluator function table. A
|
|
231
|
+
* prefixed name is resolved through the stylesheet's namespace bindings,
|
|
232
|
+
* just as a call of that function would be. A function can report itself
|
|
233
|
+
* unavailable through an `isAvailable()` property (`dyn:evaluate()` does
|
|
234
|
+
* until the engine enables it).
|
|
235
|
+
*/
|
|
164
236
|
"function-available": (args, ctx) => {
|
|
165
|
-
const
|
|
166
|
-
|
|
237
|
+
const { prefix, localName } = splitQName(asString(args[0], ctx));
|
|
238
|
+
const fn = evaluator.resolveFunction(localName, prefix, ctx.namespaces);
|
|
239
|
+
return fn !== null && fn.isAvailable?.() !== false;
|
|
167
240
|
},
|
|
168
241
|
|
|
169
242
|
/** `element-available(name)` - reflects the XSLT elements the engine runs. */
|
|
170
243
|
"element-available": (args, ctx) => {
|
|
171
244
|
const { prefix, localName } = splitQName(asString(args[0], ctx));
|
|
172
|
-
|
|
245
|
+
// An unprefixed name is in the default namespace (XSLT 1.0 section
|
|
246
|
+
// 15), which may be the XSLT namespace itself (libxslt bug-200)
|
|
247
|
+
const namespaceUri = prefix
|
|
248
|
+
? (ctx.namespaces[prefix] ?? (prefix === "xsl" ? XSLT_NAMESPACE : null))
|
|
249
|
+
: ctx.namespaces[""] || null;
|
|
250
|
+
if (!namespaceUri) return false;
|
|
173
251
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
252
|
+
if (namespaceUri === XSLT_NAMESPACE) {
|
|
253
|
+
return isXsltElementAvailable(localName);
|
|
254
|
+
}
|
|
255
|
+
// Extension elements with an implementation (registerExtensionElement)
|
|
256
|
+
return Boolean(
|
|
257
|
+
engine.extensionElements?.has(`{${namespaceUri}}${localName}`),
|
|
179
258
|
);
|
|
180
259
|
},
|
|
181
260
|
|
package/src/xslt/index.js
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
* Based on W3C XSLT 1.0 Specification: http://www.w3.org/TR/1999/REC-xslt-19991116
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export {
|
|
6
|
+
export {
|
|
7
|
+
XSLT_MAX_EXPRESSION_DEPTH,
|
|
8
|
+
XSLT_MAX_RESULT_SIZE,
|
|
9
|
+
XSLT_MAX_TEMPLATE_DEPTH,
|
|
10
|
+
XsltContext,
|
|
11
|
+
XsltEngine,
|
|
12
|
+
} from "./engine.js";
|
|
7
13
|
|
|
8
14
|
// XSLT vocabulary and function library (advanced usage)
|
|
9
15
|
export {
|
package/src/xslt/keys.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `xsl:key` indexing for the XSLT `key()` function.
|
|
3
3
|
*
|
|
4
|
-
* Indexes are built lazily, once per (
|
|
5
|
-
* `WeakMap` so source documents stay garbage collectable. The
|
|
4
|
+
* Indexes are built lazily, once per (root node, key name) pair, and cached in
|
|
5
|
+
* a `WeakMap` so source documents stay garbage collectable. The root node is
|
|
6
|
+
* the document of the context node, or the DocumentFragment of a result tree
|
|
7
|
+
* fragment converted with `exsl:node-set()` (see `rootNodeOf` in xpath/axes). The registry is
|
|
6
8
|
* deliberately decoupled from the engine: pattern matching and `use` evaluation
|
|
7
9
|
* are injected as callbacks.
|
|
8
10
|
*/
|
|
@@ -10,14 +12,27 @@
|
|
|
10
12
|
"use strict";
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
|
-
*
|
|
15
|
+
* @typedef {Object} KeyDefinition
|
|
16
|
+
* @property {string} match - Pattern of the nodes to index
|
|
17
|
+
* @property {string} use - Expression computing the key values
|
|
18
|
+
* @property {Object<string, string>} [namespaces] - Prefixes in scope on the xsl:key element
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {Object} KeyIndex
|
|
23
|
+
* @property {Map<string, Node[]>} buckets - Key value to nodes, in document order
|
|
24
|
+
* @property {Map<Node, number>} order - Document order position of every node
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Lazily built, per-tree indexes for all declared keys.
|
|
14
29
|
*/
|
|
15
30
|
export class KeyIndexRegistry {
|
|
16
31
|
/**
|
|
17
32
|
* @param {Object} options - Registry configuration
|
|
18
|
-
* @param {Object<string,
|
|
19
|
-
* @param {(node: Node, pattern: string) => boolean} options.matchesPattern - XSLT pattern matcher
|
|
20
|
-
* @param {(node: Node, expression: string) => string[]} options.evaluateUse - `use` evaluator returning key values
|
|
33
|
+
* @param {Object<string, (KeyDefinition|KeyDefinition[])>} options.keys - Declared keys by name; several xsl:key elements may share a name
|
|
34
|
+
* @param {(node: Node, pattern: string, definition: KeyDefinition) => boolean} options.matchesPattern - XSLT pattern matcher
|
|
35
|
+
* @param {(node: Node, expression: string, definition: KeyDefinition) => string[]} options.evaluateUse - `use` evaluator returning key values
|
|
21
36
|
*/
|
|
22
37
|
constructor({ keys, matchesPattern, evaluateUse }) {
|
|
23
38
|
this.keys = keys;
|
|
@@ -43,7 +58,7 @@ export class KeyIndexRegistry {
|
|
|
43
58
|
*
|
|
44
59
|
* @param {string} name - The key name
|
|
45
60
|
* @param {string|string[]} values - One key value, or several to union
|
|
46
|
-
* @param {
|
|
61
|
+
* @param {Node} doc - Root of the tree to search (Document or DocumentFragment)
|
|
47
62
|
* @returns {Node[]} Matching nodes in document order, without duplicates
|
|
48
63
|
* @throws {Error} When the key name was never declared
|
|
49
64
|
*
|
|
@@ -55,25 +70,23 @@ export class KeyIndexRegistry {
|
|
|
55
70
|
throw new Error(`Undefined key: ${name}`);
|
|
56
71
|
}
|
|
57
72
|
|
|
58
|
-
const
|
|
73
|
+
const { buckets, order } = this.getIndex(name, doc);
|
|
59
74
|
const wanted = Array.isArray(values) ? values : [values];
|
|
60
|
-
|
|
75
|
+
if (wanted.length === 1) return [...(buckets.get(wanted[0]) || [])];
|
|
61
76
|
|
|
77
|
+
const found = new Set();
|
|
62
78
|
for (const value of wanted) {
|
|
63
|
-
for (const node of
|
|
64
|
-
if (!result.includes(node)) result.push(node);
|
|
65
|
-
}
|
|
79
|
+
for (const node of buckets.get(value) || []) found.add(node);
|
|
66
80
|
}
|
|
67
|
-
|
|
68
|
-
return result;
|
|
81
|
+
return [...found].sort((a, b) => order.get(a) - order.get(b));
|
|
69
82
|
}
|
|
70
83
|
|
|
71
84
|
/**
|
|
72
85
|
* Get (building if needed) the index of one key for one document.
|
|
73
86
|
*
|
|
74
87
|
* @param {string} name - The key name
|
|
75
|
-
* @param {
|
|
76
|
-
* @returns {
|
|
88
|
+
* @param {Node} doc - Root of the tree being indexed
|
|
89
|
+
* @returns {KeyIndex} The index
|
|
77
90
|
*/
|
|
78
91
|
getIndex(name, doc) {
|
|
79
92
|
let byName = this.cache.get(doc);
|
|
@@ -92,27 +105,37 @@ export class KeyIndexRegistry {
|
|
|
92
105
|
}
|
|
93
106
|
|
|
94
107
|
/**
|
|
95
|
-
* Build the index of one key for one document
|
|
108
|
+
* Build the index of one key for one document, merging every declaration
|
|
109
|
+
* of that key name.
|
|
96
110
|
*
|
|
97
111
|
* @param {string} name - The key name
|
|
98
|
-
* @param {
|
|
99
|
-
* @returns {
|
|
112
|
+
* @param {Node} doc - Root of the tree being indexed
|
|
113
|
+
* @returns {KeyIndex} The index
|
|
100
114
|
*/
|
|
101
115
|
buildIndex(name, doc) {
|
|
102
|
-
const
|
|
103
|
-
const
|
|
116
|
+
const definitions = [this.keys[name]].flat();
|
|
117
|
+
const buckets = new Map();
|
|
118
|
+
const order = new Map();
|
|
104
119
|
|
|
105
120
|
for (const node of documentOrderNodes(doc)) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
for (const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
121
|
+
order.set(node, order.size);
|
|
122
|
+
|
|
123
|
+
for (const definition of definitions) {
|
|
124
|
+
if (!this.matchesPattern(node, definition.match, definition)) continue;
|
|
125
|
+
|
|
126
|
+
for (const value of this.evaluateUse(
|
|
127
|
+
node,
|
|
128
|
+
definition.use,
|
|
129
|
+
definition,
|
|
130
|
+
)) {
|
|
131
|
+
const bucket = buckets.get(value);
|
|
132
|
+
if (!bucket) buckets.set(value, [node]);
|
|
133
|
+
else if (bucket[bucket.length - 1] !== node) bucket.push(node);
|
|
134
|
+
}
|
|
112
135
|
}
|
|
113
136
|
}
|
|
114
137
|
|
|
115
|
-
return
|
|
138
|
+
return { buckets, order };
|
|
116
139
|
}
|
|
117
140
|
}
|
|
118
141
|
|
|
@@ -42,10 +42,20 @@ export function lookupNamespaceUri(node, prefix) {
|
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* The `xsl:namespace-alias` declarations of a stylesheet.
|
|
45
|
+
*
|
|
46
|
+
* Literal result names are aliased as libxslt (and so Chrome) does, see
|
|
47
|
+
* {@link NamespaceAliasMap#resolveLiteral}: the namespace URI is replaced and
|
|
48
|
+
* the prefix written in the stylesheet is kept (`axsl:stylesheet` with
|
|
49
|
+
* `xmlns:axsl` bound to the XSLT namespace); `result-prefix="#default"`
|
|
50
|
+
* without a default namespace in scope gives names in no namespace, and
|
|
51
|
+
* `stylesheet-prefix="#default"` without a default namespace in scope
|
|
52
|
+
* aliases the elements in no namespace, which then take the result prefix.
|
|
45
53
|
*/
|
|
46
54
|
export class NamespaceAliasMap {
|
|
47
55
|
constructor() {
|
|
48
56
|
this.byUri = new Map();
|
|
57
|
+
/** Alias of elements in no namespace (`#default` stylesheet prefix). */
|
|
58
|
+
this.noNamespaceAlias = null;
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
/**
|
|
@@ -62,22 +72,22 @@ export class NamespaceAliasMap {
|
|
|
62
72
|
const resultPrefix = node.getAttribute("result-prefix");
|
|
63
73
|
if (!stylesheetPrefix || !resultPrefix) return;
|
|
64
74
|
|
|
75
|
+
const isDefaultSource = stylesheetPrefix === "#default";
|
|
65
76
|
const fromUri = lookupNamespaceUri(
|
|
66
77
|
node,
|
|
67
|
-
|
|
78
|
+
isDefaultSource ? null : stylesheetPrefix,
|
|
68
79
|
);
|
|
69
|
-
if (!fromUri) return;
|
|
80
|
+
if (!fromUri && !isDefaultSource) return;
|
|
70
81
|
|
|
71
82
|
const isDefaultResult = resultPrefix === "#default";
|
|
72
83
|
const toUri = lookupNamespaceUri(
|
|
73
84
|
node,
|
|
74
85
|
isDefaultResult ? null : resultPrefix,
|
|
75
86
|
);
|
|
87
|
+
const alias = { uri: toUri, prefix: isDefaultResult ? null : resultPrefix };
|
|
76
88
|
|
|
77
|
-
this.byUri.set(fromUri,
|
|
78
|
-
|
|
79
|
-
prefix: isDefaultResult ? null : resultPrefix,
|
|
80
|
-
});
|
|
89
|
+
if (fromUri) this.byUri.set(fromUri, alias);
|
|
90
|
+
else if (toUri) this.noNamespaceAlias = alias;
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
/**
|
|
@@ -89,7 +99,20 @@ export class NamespaceAliasMap {
|
|
|
89
99
|
* aliases.isEmpty();
|
|
90
100
|
*/
|
|
91
101
|
isEmpty() {
|
|
92
|
-
return this.byUri.size === 0;
|
|
102
|
+
return this.byUri.size === 0 && this.noNamespaceAlias === null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Whether a namespace is the stylesheet side of an alias.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} uri - A namespace URI
|
|
109
|
+
* @returns {boolean} True when literal result names in it are aliased
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* aliases.isAliased('http://www.w3.org/1999/XSL/TransformAlias'); // true
|
|
113
|
+
*/
|
|
114
|
+
isAliased(uri) {
|
|
115
|
+
return this.byUri.has(uri);
|
|
93
116
|
}
|
|
94
117
|
|
|
95
118
|
/**
|
|
@@ -112,6 +135,39 @@ export class NamespaceAliasMap {
|
|
|
112
135
|
qname: alias.prefix ? `${alias.prefix}:${localName}` : localName,
|
|
113
136
|
};
|
|
114
137
|
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Apply aliasing to the name of a literal result element or attribute as
|
|
141
|
+
* libxslt does: the namespace URI is replaced and the stylesheet prefix
|
|
142
|
+
* kept. An element in no namespace takes the `#default` stylesheet-prefix
|
|
143
|
+
* alias with its result prefix; an alias to no namespace drops the prefix.
|
|
144
|
+
*
|
|
145
|
+
* @param {Element|Attr} node - The literal result element or attribute
|
|
146
|
+
* @returns {{namespaceUri: (string|null), qname: string}|null} The aliased
|
|
147
|
+
* name, or null when no alias applies
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* // xmlns:axsl="urn:alias", <xsl:namespace-alias stylesheet-prefix="axsl"
|
|
151
|
+
* // result-prefix="xsl"/>
|
|
152
|
+
* aliases.resolveLiteral(axslStylesheetElement);
|
|
153
|
+
* // { namespaceUri: 'http://www.w3.org/1999/XSL/Transform', qname: 'axsl:stylesheet' }
|
|
154
|
+
*/
|
|
155
|
+
resolveLiteral(node) {
|
|
156
|
+
const { localName } = node;
|
|
157
|
+
if (!node.namespaceURI) {
|
|
158
|
+
const alias = node.nodeType === 1 ? this.noNamespaceAlias : null;
|
|
159
|
+
return alias
|
|
160
|
+
? { namespaceUri: alias.uri, qname: `${alias.prefix}:${localName}` }
|
|
161
|
+
: null;
|
|
162
|
+
}
|
|
163
|
+
const alias = this.byUri.get(node.namespaceURI);
|
|
164
|
+
if (!alias) return null;
|
|
165
|
+
if (!alias.uri) return { namespaceUri: null, qname: localName };
|
|
166
|
+
return {
|
|
167
|
+
namespaceUri: alias.uri,
|
|
168
|
+
qname: node.prefix ? `${node.prefix}:${localName}` : localName,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
115
171
|
}
|
|
116
172
|
|
|
117
173
|
/**
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers of the XSLT pattern matcher (see patterns.js): node relationships
|
|
3
|
+
* in the XPath data model and the per-call {@link MatchScope} that builds the
|
|
4
|
+
* XPath contexts predicates and `id()`/`key()` anchors are evaluated in.
|
|
5
|
+
*
|
|
6
|
+
* @module xslt/matchScope
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
import { XPathContext } from "../xpath/evaluator.js";
|
|
12
|
+
import { parentOf } from "../xpath/axes.js";
|
|
13
|
+
|
|
14
|
+
const XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/";
|
|
15
|
+
const EMPTY_VARIABLES = Object.freeze({});
|
|
16
|
+
const EMPTY_NAMESPACES = Object.freeze({});
|
|
17
|
+
|
|
18
|
+
export { parentOf };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The root of the tree holding a node (its document, or the top of a
|
|
22
|
+
* detached subtree or result tree fragment).
|
|
23
|
+
*
|
|
24
|
+
* @param {Node} node - Any node
|
|
25
|
+
* @returns {Node} The outermost ancestor
|
|
26
|
+
*/
|
|
27
|
+
export function rootOf(node) {
|
|
28
|
+
let root = node;
|
|
29
|
+
for (let parent = parentOf(root); parent; parent = parentOf(parent)) {
|
|
30
|
+
root = parent;
|
|
31
|
+
}
|
|
32
|
+
return root;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether a node is a root node (a document or a result tree fragment).
|
|
37
|
+
*
|
|
38
|
+
* @param {Node|null} node - Any node
|
|
39
|
+
* @returns {boolean} True for document and document fragment nodes
|
|
40
|
+
*/
|
|
41
|
+
export function isRoot(node) {
|
|
42
|
+
return node !== null && (node.nodeType === 9 || node.nodeType === 11);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Whether an attribute is a namespace declaration, which XPath does not
|
|
47
|
+
* expose on the attribute axis.
|
|
48
|
+
*
|
|
49
|
+
* @param {Attr} attribute - An attribute node
|
|
50
|
+
* @returns {boolean} True for `xmlns` and `xmlns:*` attributes
|
|
51
|
+
*/
|
|
52
|
+
export function isNamespaceDeclaration(attribute) {
|
|
53
|
+
return (
|
|
54
|
+
attribute.namespaceURI === XMLNS_NAMESPACE ||
|
|
55
|
+
attribute.name === "xmlns" ||
|
|
56
|
+
attribute.name.startsWith("xmlns:")
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* State of a single match call: lazily merges the XSLT variables once.
|
|
62
|
+
*/
|
|
63
|
+
export class MatchScope {
|
|
64
|
+
/**
|
|
65
|
+
* @param {object|null} host - XSLT context (variables, parameters, namespaces)
|
|
66
|
+
* @param {Object<string, string>} [namespaces] - Prefix bindings, when they
|
|
67
|
+
* differ from the host's (e.g. those in scope on an xsl:template)
|
|
68
|
+
*/
|
|
69
|
+
constructor(host, namespaces) {
|
|
70
|
+
this.host = host;
|
|
71
|
+
this.namespaces = namespaces ?? host?.namespaces ?? EMPTY_NAMESPACES;
|
|
72
|
+
this.variables = null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build an XPath context for evaluating a predicate or anchor call.
|
|
77
|
+
*
|
|
78
|
+
* @param {Node} node - Context node
|
|
79
|
+
* @param {number} position - Context position
|
|
80
|
+
* @param {number} size - Context size
|
|
81
|
+
* @returns {XPathContext} The evaluation context
|
|
82
|
+
*/
|
|
83
|
+
context(node, position, size) {
|
|
84
|
+
if (!this.variables) {
|
|
85
|
+
this.variables = this.host?.xpathVariables ?? {
|
|
86
|
+
...this.host?.variables,
|
|
87
|
+
...this.host?.parameters,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return new XPathContext(
|
|
91
|
+
node,
|
|
92
|
+
position,
|
|
93
|
+
size,
|
|
94
|
+
this.variables,
|
|
95
|
+
this.namespaces,
|
|
96
|
+
this.host,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Build a cheap XPath context for node tests (namespaces only).
|
|
102
|
+
*
|
|
103
|
+
* @param {Node} node - Context node
|
|
104
|
+
* @returns {XPathContext} The evaluation context
|
|
105
|
+
*/
|
|
106
|
+
testContext(node) {
|
|
107
|
+
return new XPathContext(
|
|
108
|
+
node,
|
|
109
|
+
1,
|
|
110
|
+
1,
|
|
111
|
+
EMPTY_VARIABLES,
|
|
112
|
+
this.namespaces,
|
|
113
|
+
this.host,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|