@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,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-scope namespaces of stylesheet elements.
|
|
3
|
+
*
|
|
4
|
+
* Prefixes in XPath expressions, patterns and QName-valued attributes resolve
|
|
5
|
+
* against the namespace declarations in scope on the element that carries
|
|
6
|
+
* them (XSLT 1.0 section 2.4), not against one stylesheet-wide table: a
|
|
7
|
+
* declaration on `xsl:template` or on a literal result element applies inside
|
|
8
|
+
* it only, and an included stylesheet may bind a prefix differently from the
|
|
9
|
+
* including one. The same bindings are the namespace nodes that literal
|
|
10
|
+
* result elements copy to the result tree (section 7.1.1), minus the XSLT
|
|
11
|
+
* namespace and the excluded and extension prefixes.
|
|
12
|
+
*
|
|
13
|
+
* Every map is computed once per element and cached; an element without
|
|
14
|
+
* declarations of its own shares its parent's (frozen) map, so identical
|
|
15
|
+
* scopes can be recognised by identity.
|
|
16
|
+
*
|
|
17
|
+
* @module xslt/stylesheetNamespaces
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
"use strict";
|
|
21
|
+
|
|
22
|
+
import { XSLT_NAMESPACE } from "./elements.js";
|
|
23
|
+
|
|
24
|
+
/** Namespace of `xmlns` attributes. */
|
|
25
|
+
export const XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/";
|
|
26
|
+
|
|
27
|
+
/** Namespace bound to the `xml` prefix. */
|
|
28
|
+
export const XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace";
|
|
29
|
+
|
|
30
|
+
/** The scope above the stylesheet document element. */
|
|
31
|
+
const EMPTY_SCOPE = Object.freeze({});
|
|
32
|
+
|
|
33
|
+
/** Namespaces excluded above the stylesheet document element. */
|
|
34
|
+
const BASE_EXCLUSIONS = new Set([XSLT_NAMESPACE]);
|
|
35
|
+
|
|
36
|
+
/** No extension namespaces, above the stylesheet document element. */
|
|
37
|
+
const NO_EXTENSIONS = new Set();
|
|
38
|
+
|
|
39
|
+
const scopes = new WeakMap();
|
|
40
|
+
const exclusions = new WeakMap();
|
|
41
|
+
const extensions = new WeakMap();
|
|
42
|
+
const namespaceNodes = new WeakMap();
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The prefix a namespace declaration attribute declares, or null when the
|
|
46
|
+
* attribute is not a declaration.
|
|
47
|
+
*
|
|
48
|
+
* @param {Attr} attribute - Any attribute
|
|
49
|
+
* @returns {string|null} The prefix, "" for a default namespace declaration
|
|
50
|
+
*/
|
|
51
|
+
export function declaredPrefix(attribute) {
|
|
52
|
+
const name = attribute.name;
|
|
53
|
+
if (name === "xmlns") return "";
|
|
54
|
+
if (name.startsWith("xmlns:")) return name.slice(6);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The namespace bindings in scope on a stylesheet element.
|
|
60
|
+
*
|
|
61
|
+
* @param {Node|null} node - A stylesheet element (other nodes have no scope)
|
|
62
|
+
* @returns {Readonly<Object<string, string>>} URIs by prefix; "" holds the
|
|
63
|
+
* default namespace, an empty URI means "undeclared"
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* inScopeNamespaces(templateElement).f; // "urn:f"
|
|
67
|
+
*/
|
|
68
|
+
export function inScopeNamespaces(node) {
|
|
69
|
+
if (node?.nodeType !== 1) return EMPTY_SCOPE;
|
|
70
|
+
|
|
71
|
+
let scope = scopes.get(node);
|
|
72
|
+
if (scope) return scope;
|
|
73
|
+
|
|
74
|
+
const parentScope = inScopeNamespaces(node.parentNode);
|
|
75
|
+
let own = null;
|
|
76
|
+
for (const attribute of node.attributes) {
|
|
77
|
+
const prefix = declaredPrefix(attribute);
|
|
78
|
+
if (prefix === null) continue;
|
|
79
|
+
own ??= {};
|
|
80
|
+
own[prefix] = attribute.value;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
scope = own ? Object.freeze({ ...parentScope, ...own }) : parentScope;
|
|
84
|
+
scopes.set(node, scope);
|
|
85
|
+
return scope;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Split an XSLT attribute holding whitespace separated prefixes.
|
|
90
|
+
*
|
|
91
|
+
* @param {string|null} value - e.g. `"a b #default"`
|
|
92
|
+
* @returns {string[]} The prefixes, "" standing for `#default`
|
|
93
|
+
*/
|
|
94
|
+
function prefixList(value) {
|
|
95
|
+
if (!value) return [];
|
|
96
|
+
return value
|
|
97
|
+
.split(/[ \t\r\n]+/)
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.map((prefix) => (prefix === "#default" ? "" : prefix));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Attributes whose prefixes are excluded from the result tree. */
|
|
103
|
+
const EXCLUSION_ATTRIBUTES = [
|
|
104
|
+
"exclude-result-prefixes",
|
|
105
|
+
"extension-element-prefixes",
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
/** Attributes declaring extension namespaces (XSLT 1.0 section 14.1). */
|
|
109
|
+
const EXTENSION_ATTRIBUTES = ["extension-element-prefixes"];
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Read prefix list attributes of one stylesheet element: unqualified on
|
|
113
|
+
* `xsl:stylesheet`, `xsl:`-qualified on literal result elements.
|
|
114
|
+
*
|
|
115
|
+
* @param {Element} element - A stylesheet element
|
|
116
|
+
* @param {string[]} attributes - Local names of the attributes to read
|
|
117
|
+
* @returns {Array<{prefix: string, attribute: string}>} Prefixes declared on
|
|
118
|
+
* this element, with the attribute declaring each
|
|
119
|
+
*/
|
|
120
|
+
function ownPrefixes(element, attributes) {
|
|
121
|
+
const isXslt = element.namespaceURI === XSLT_NAMESPACE;
|
|
122
|
+
const read = (localName) =>
|
|
123
|
+
isXslt
|
|
124
|
+
? element.getAttribute(localName)
|
|
125
|
+
: element.getAttributeNS(XSLT_NAMESPACE, localName);
|
|
126
|
+
return attributes.flatMap((attribute) =>
|
|
127
|
+
prefixList(read(attribute)).map((prefix) => ({ prefix, attribute })),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Namespace URIs named by prefix list attributes on a stylesheet element or
|
|
133
|
+
* its ancestors, cached per element.
|
|
134
|
+
*
|
|
135
|
+
* @param {Node|null} node - A stylesheet element
|
|
136
|
+
* @param {object} kind - What to collect
|
|
137
|
+
* @param {string[]} kind.attributes - The prefix list attributes
|
|
138
|
+
* @param {WeakMap<Element, Set<string>>} kind.cache - Results by element
|
|
139
|
+
* @param {Set<string>} kind.base - The set above the document element
|
|
140
|
+
* @param {boolean} kind.report - Whether undeclared prefixes are reported
|
|
141
|
+
* @returns {Set<string>} The namespace URIs
|
|
142
|
+
*/
|
|
143
|
+
function declaredNamespaces(node, kind) {
|
|
144
|
+
if (node?.nodeType !== 1) return kind.base;
|
|
145
|
+
|
|
146
|
+
let found = kind.cache.get(node);
|
|
147
|
+
if (found) return found;
|
|
148
|
+
|
|
149
|
+
found = declaredNamespaces(node.parentNode, kind);
|
|
150
|
+
const prefixes = ownPrefixes(node, kind.attributes);
|
|
151
|
+
if (prefixes.length > 0) {
|
|
152
|
+
const scope = inScopeNamespaces(node);
|
|
153
|
+
found = new Set(found);
|
|
154
|
+
for (const { prefix, attribute } of prefixes) {
|
|
155
|
+
const uri = resolvePrefix(scope, prefix);
|
|
156
|
+
if (uri) {
|
|
157
|
+
found.add(uri);
|
|
158
|
+
} else if (kind.report) {
|
|
159
|
+
// An error in XSLT 1.0 (section 7.1.1) that libxslt reports and
|
|
160
|
+
// recovers from; reported once, as the result is cached per element
|
|
161
|
+
console.warn(
|
|
162
|
+
`XSLT: ${attribute}: undefined namespace prefix "${prefix || "#default"}" is ignored`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
kind.cache.set(node, found);
|
|
168
|
+
return found;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Excluded namespaces: excluded and extension prefixes, plus XSLT. */
|
|
172
|
+
const EXCLUDED = {
|
|
173
|
+
attributes: EXCLUSION_ATTRIBUTES,
|
|
174
|
+
cache: exclusions,
|
|
175
|
+
base: BASE_EXCLUSIONS,
|
|
176
|
+
report: true,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/** Extension namespaces (reported through EXCLUDED already). */
|
|
180
|
+
const EXTENSIONS = {
|
|
181
|
+
attributes: EXTENSION_ATTRIBUTES,
|
|
182
|
+
cache: extensions,
|
|
183
|
+
base: NO_EXTENSIONS,
|
|
184
|
+
report: false,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Namespace URIs excluded from the result tree around a stylesheet element:
|
|
189
|
+
* the XSLT namespace, plus the namespaces of every excluded or extension
|
|
190
|
+
* prefix declared on the element or its ancestors. A prefix that is not
|
|
191
|
+
* declared (including `#all`, which XSLT 1.0 does not know) is reported with
|
|
192
|
+
* console.warn and ignored.
|
|
193
|
+
*
|
|
194
|
+
* @param {Node|null} node - A stylesheet element
|
|
195
|
+
* @returns {Set<string>} Excluded namespace URIs
|
|
196
|
+
*/
|
|
197
|
+
function excludedNamespaces(node) {
|
|
198
|
+
return declaredNamespaces(node, EXCLUDED);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Whether a stylesheet element is an extension element: an element in a
|
|
203
|
+
* namespace that an `extension-element-prefixes` attribute on it or on an
|
|
204
|
+
* ancestor declares as an extension namespace (XSLT 1.0 section 14.1).
|
|
205
|
+
*
|
|
206
|
+
* @param {Element} element - A stylesheet element that is not in the XSLT namespace
|
|
207
|
+
* @returns {boolean} True for extension elements
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* // <xsl:stylesheet xmlns:e="urn:e" extension-element-prefixes="e">
|
|
211
|
+
* isExtensionElement(eElement); // true
|
|
212
|
+
*/
|
|
213
|
+
export function isExtensionElement(element) {
|
|
214
|
+
const uri = element.namespaceURI;
|
|
215
|
+
if (!uri) return false;
|
|
216
|
+
// Resolving the exclusions first reports undeclared prefixes once
|
|
217
|
+
excludedNamespaces(element);
|
|
218
|
+
return declaredNamespaces(element, EXTENSIONS).has(uri);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* The namespace nodes a literal result element copies to the result tree
|
|
223
|
+
* (XSLT 1.0 section 7.1.1).
|
|
224
|
+
*
|
|
225
|
+
* @param {Element} node - The literal result element
|
|
226
|
+
* The list is shared by every element with the same scope and exclusions,
|
|
227
|
+
* so equal lists can be compared by identity.
|
|
228
|
+
*
|
|
229
|
+
* @param {Element} node - The literal result element
|
|
230
|
+
* @returns {Array<[string, string]>} `[prefix, uri]` pairs, "" for the default namespace
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* resultNamespaceNodes(literalElement); // [["q", "urn:q"]]
|
|
234
|
+
*/
|
|
235
|
+
export function resultNamespaceNodes(node) {
|
|
236
|
+
const scope = inScopeNamespaces(node);
|
|
237
|
+
const excluded = excludedNamespaces(node);
|
|
238
|
+
|
|
239
|
+
let byExclusions = namespaceNodes.get(scope);
|
|
240
|
+
if (!byExclusions) {
|
|
241
|
+
byExclusions = new WeakMap();
|
|
242
|
+
namespaceNodes.set(scope, byExclusions);
|
|
243
|
+
}
|
|
244
|
+
let nodes = byExclusions.get(excluded);
|
|
245
|
+
if (!nodes) {
|
|
246
|
+
nodes = Object.entries(scope).filter(
|
|
247
|
+
([, uri]) => uri !== "" && !excluded.has(uri),
|
|
248
|
+
);
|
|
249
|
+
byExclusions.set(excluded, nodes);
|
|
250
|
+
}
|
|
251
|
+
return nodes;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Resolve a QName prefix against a scope.
|
|
256
|
+
*
|
|
257
|
+
* @param {Object<string, string>} scope - Bindings from {@link inScopeNamespaces}
|
|
258
|
+
* @param {string} prefix - The prefix, "" for the default namespace
|
|
259
|
+
* @returns {string|null} The namespace URI, or null when unbound
|
|
260
|
+
*/
|
|
261
|
+
export function resolvePrefix(scope, prefix) {
|
|
262
|
+
if (prefix === "xml") return XML_NAMESPACE;
|
|
263
|
+
return Object.hasOwn(scope, prefix) && scope[prefix] !== ""
|
|
264
|
+
? scope[prefix]
|
|
265
|
+
: null;
|
|
266
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variable and parameter bindings (XSLT 1.0 section 11).
|
|
3
|
+
*
|
|
4
|
+
* Local bindings live in the `variables` and `parameters` objects of an
|
|
5
|
+
* XSLT context; the engine gives every template invocation empty ones and a
|
|
6
|
+
* new copy to every sequence constructor that declares a variable, which
|
|
7
|
+
* makes local variables lexically scoped. Global variables and parameters are
|
|
8
|
+
* kept in a {@link GlobalBindings} table shared by all contexts of one
|
|
9
|
+
* transformation and are evaluated lazily, so they may refer to each other in
|
|
10
|
+
* any order (section 11.4).
|
|
11
|
+
*
|
|
12
|
+
* @module xslt/variables
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
"use strict";
|
|
16
|
+
|
|
17
|
+
/** Evaluation states of a global binding. */
|
|
18
|
+
const PENDING = 0;
|
|
19
|
+
const EVALUATING = 1;
|
|
20
|
+
const DONE = 2;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The global variables and parameters of one transformation.
|
|
24
|
+
*/
|
|
25
|
+
export class GlobalBindings {
|
|
26
|
+
/**
|
|
27
|
+
* @param {(definition: object) => *} evaluate - Computes the value of a
|
|
28
|
+
* definition; called at most once per name
|
|
29
|
+
*/
|
|
30
|
+
constructor(evaluate) {
|
|
31
|
+
this.evaluate = evaluate;
|
|
32
|
+
this.entries = new Map();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Declare a global binding. A later declaration of the same name replaces
|
|
37
|
+
* an earlier one.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} name - The variable or parameter name
|
|
40
|
+
* @param {object} definition - Passed to the evaluate callback
|
|
41
|
+
* @returns {void}
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* globals.define('title', { select: "'Report'" });
|
|
45
|
+
*/
|
|
46
|
+
define(name, definition) {
|
|
47
|
+
this.entries.set(name, { definition, state: PENDING, value: undefined });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether a global binding with that name exists.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} name - The variable name
|
|
54
|
+
* @returns {boolean} True when declared
|
|
55
|
+
*/
|
|
56
|
+
has(name) {
|
|
57
|
+
return this.entries.has(name);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The value of a global binding, evaluating it on first use.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} name - The variable name
|
|
64
|
+
* @returns {*} The value, undefined when the name is not declared
|
|
65
|
+
* @throws {Error} When the definition (indirectly) refers to itself
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* globals.get('title'); // "Report"
|
|
69
|
+
*/
|
|
70
|
+
get(name) {
|
|
71
|
+
const entry = this.entries.get(name);
|
|
72
|
+
if (!entry) return undefined;
|
|
73
|
+
if (entry.state === DONE) return entry.value;
|
|
74
|
+
if (entry.state === EVALUATING) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Circular definition of global variable $${name} (XSLT 1.0 section 11.4)`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
entry.state = EVALUATING;
|
|
81
|
+
try {
|
|
82
|
+
entry.value = this.evaluate(entry.definition);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
entry.state = PENDING;
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
entry.state = DONE;
|
|
88
|
+
return entry.value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Evaluate every binding, in declaration order, so that errors and
|
|
93
|
+
* `xsl:message` side effects of unused globals still surface.
|
|
94
|
+
*
|
|
95
|
+
* @returns {void}
|
|
96
|
+
*/
|
|
97
|
+
evaluateAll() {
|
|
98
|
+
for (const name of this.entries.keys()) this.get(name);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Look a variable up in an XSLT context: local variables, then local
|
|
104
|
+
* parameters, then the globals.
|
|
105
|
+
*
|
|
106
|
+
* @param {{variables: object, parameters: object, globals?: GlobalBindings|null}} context - The XSLT context
|
|
107
|
+
* @param {string} name - The variable name
|
|
108
|
+
* @returns {{found: boolean, value: *}} Whether it is bound, and its value
|
|
109
|
+
*/
|
|
110
|
+
export function lookupVariable(context, name) {
|
|
111
|
+
if (Object.hasOwn(context.variables, name)) {
|
|
112
|
+
return { found: true, value: context.variables[name] };
|
|
113
|
+
}
|
|
114
|
+
if (Object.hasOwn(context.parameters, name)) {
|
|
115
|
+
return { found: true, value: context.parameters[name] };
|
|
116
|
+
}
|
|
117
|
+
const globals = context.globals;
|
|
118
|
+
if (globals?.has(name)) return { found: true, value: globals.get(name) };
|
|
119
|
+
return { found: false, value: undefined };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Read-only object view of the variables in scope of an XSLT context, in the
|
|
124
|
+
* shape the XPath evaluator expects (`hasOwnProperty` + property access).
|
|
125
|
+
* Nothing is copied: lookups read the context live, and globals are only
|
|
126
|
+
* evaluated when an expression actually refers to them.
|
|
127
|
+
*
|
|
128
|
+
* @param {object} context - The XSLT context
|
|
129
|
+
* @returns {object} A proxy mapping variable names to values
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* new XPathContext(node, 1, 1, createVariableView(xsltContext), namespaces);
|
|
133
|
+
*/
|
|
134
|
+
export function createVariableView(context) {
|
|
135
|
+
const binding = (name) =>
|
|
136
|
+
typeof name === "string" ? lookupVariable(context, name) : null;
|
|
137
|
+
|
|
138
|
+
return new Proxy(Object.create(null), {
|
|
139
|
+
has: (_target, name) => binding(name)?.found === true,
|
|
140
|
+
get: (_target, name) => binding(name)?.value,
|
|
141
|
+
getOwnPropertyDescriptor: (_target, name) => {
|
|
142
|
+
const found = binding(name);
|
|
143
|
+
if (!found?.found) return undefined;
|
|
144
|
+
return {
|
|
145
|
+
value: found.value,
|
|
146
|
+
writable: false,
|
|
147
|
+
enumerable: true,
|
|
148
|
+
configurable: true,
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|