@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
package/src/XSLTProcessor.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* Native-compatible XSLTProcessor implementation for browser environments.
|
|
5
5
|
* Based on W3C DOM Level 3 XSL Transformations and XSLT 1.0 Specification.
|
|
6
6
|
*
|
|
7
|
+
* With the `xsltVersion: "auto"` option, XSLT 2.0/3.0 stylesheets are run by
|
|
8
|
+
* the optional peer dependency @tradik/xslt3 (see bridge/).
|
|
9
|
+
*
|
|
7
10
|
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/XSLTProcessor
|
|
8
11
|
* XSLT 1.0: http://www.w3.org/TR/1999/REC-xslt-19991116
|
|
9
12
|
* XPath 1.0: http://www.w3.org/TR/1999/REC-xpath-19991116
|
|
@@ -14,6 +17,23 @@
|
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
19
|
import { XsltEngine } from "./xslt/engine.js";
|
|
20
|
+
import { findParseError } from "./xslt/domParsing.js";
|
|
21
|
+
import { preloadedDocumentLoader } from "./async/preload.js";
|
|
22
|
+
import {
|
|
23
|
+
importStylesheetAsync,
|
|
24
|
+
transformAsync,
|
|
25
|
+
transformToStream,
|
|
26
|
+
} from "./async/processor.js";
|
|
27
|
+
import { createXslt3Engine } from "./bridge/engine.js";
|
|
28
|
+
import { loadXslt3 } from "./bridge/loader.js";
|
|
29
|
+
import { usesXslt3, xsltVersionMode } from "./bridge/version.js";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The native XSLTProcessor constructor that installGlobal() replaced, kept so
|
|
33
|
+
* that isNativeXSLTSupported() keeps probing the browser's implementation
|
|
34
|
+
* rather than this one.
|
|
35
|
+
*/
|
|
36
|
+
let nativeProcessor = null;
|
|
17
37
|
|
|
18
38
|
/**
|
|
19
39
|
* XSLTProcessor
|
|
@@ -26,10 +46,186 @@ import { XsltEngine } from "./xslt/engine.js";
|
|
|
26
46
|
* const fragment = processor.transformToFragment(xmlDoc, document);
|
|
27
47
|
*/
|
|
28
48
|
export class XSLTProcessor {
|
|
29
|
-
|
|
49
|
+
/**
|
|
50
|
+
* @param {object} [options] - Non-standard options (the native constructor
|
|
51
|
+
* takes none)
|
|
52
|
+
* @param {boolean} [options.legacyNameTests] - Deprecated: let unprefixed
|
|
53
|
+
* name tests (`item`, `@a`) also match nodes in a namespace, as before
|
|
54
|
+
* 1.2.0. XPath 1.0 and Chrome only match nodes in no namespace.
|
|
55
|
+
* @param {boolean} [options.enableDynamicEvaluate] - Allow EXSLT
|
|
56
|
+
* `dyn:evaluate()`, which evaluates XPath built from strings; enable it
|
|
57
|
+
* only for trusted input.
|
|
58
|
+
* @param {() => Date} [options.clock] - Clock for EXSLT current-time
|
|
59
|
+
* functions (reproducible output).
|
|
60
|
+
* @param {number} [options.maxTemplateDepth] - Deepest nesting of template
|
|
61
|
+
* instantiations; deeper recursion throws "Template recursion too deep"
|
|
62
|
+
* (default 3000, libxslt's limit).
|
|
63
|
+
* @param {"1.0"|"auto"} [options.xsltVersion] - "1.0" (default): every
|
|
64
|
+
* stylesheet runs with the XSLT 1.0 engine, a `version="2.0"` one in
|
|
65
|
+
* forwards-compatible mode, as in Chrome. "auto": a stylesheet whose
|
|
66
|
+
* version is 2.0 or more runs with @tradik/xslt3 (an optional peer
|
|
67
|
+
* dependency, loaded on demand; see {@link XSLTProcessor.preload}).
|
|
68
|
+
* @throws {RangeError} For an invalid `xsltVersion`
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* // Temporary migration aid for stylesheets written against 1.1.x
|
|
72
|
+
* const processor = new XSLTProcessor({ legacyNameTests: true });
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* // XSLT 2.0/3.0 stylesheets through the same API
|
|
76
|
+
* const processor = new XSLTProcessor({ xsltVersion: "auto" });
|
|
77
|
+
* await processor.importStylesheetAsync(xsl20Doc);
|
|
78
|
+
*/
|
|
79
|
+
constructor(options = {}) {
|
|
80
|
+
this._options = {
|
|
81
|
+
legacyNameTests: options?.legacyNameTests === true,
|
|
82
|
+
enableDynamicEvaluate: options?.enableDynamicEvaluate === true,
|
|
83
|
+
clock: options?.clock ?? null,
|
|
84
|
+
maxTemplateDepth: options?.maxTemplateDepth,
|
|
85
|
+
xsltVersion: xsltVersionMode(options?.xsltVersion),
|
|
86
|
+
};
|
|
30
87
|
this._engine = null;
|
|
31
88
|
this._stylesheet = null;
|
|
32
89
|
this._parameters = new Map();
|
|
90
|
+
this._stylesheetLoader = null;
|
|
91
|
+
this._documentLoader = null;
|
|
92
|
+
// Set by importStylesheet/importStylesheetAsync: the URI and modules of
|
|
93
|
+
// the stylesheet, and the document() documents preloaded asynchronously
|
|
94
|
+
this._stylesheetUri = undefined;
|
|
95
|
+
this._modules = [];
|
|
96
|
+
this._preloadedDocuments = null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Load @tradik/xslt3, so that the synchronous API of processors created
|
|
101
|
+
* with `xsltVersion: "auto"` can run XSLT 2.0/3.0 stylesheets (the
|
|
102
|
+
* asynchronous API loads it by itself). Non-W3C.
|
|
103
|
+
*
|
|
104
|
+
* @param {"2.0"|"3.0"} [version] - The XSLT version to prepare for
|
|
105
|
+
* @returns {Promise<void>} Resolves once the engine is loaded
|
|
106
|
+
* @throws {RangeError} For another version (synchronously)
|
|
107
|
+
* @throws {Error} Rejects with "Cannot load @tradik/xslt3: install
|
|
108
|
+
* @tradik/xslt3 to run XSLT 2.0/3.0 stylesheets" when it is missing
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* await XSLTProcessor.preload("3.0");
|
|
112
|
+
* const processor = new XSLTProcessor({ xsltVersion: "auto" });
|
|
113
|
+
* processor.importStylesheet(xsl30Doc);
|
|
114
|
+
*/
|
|
115
|
+
static preload(version = "3.0") {
|
|
116
|
+
if (version !== "2.0" && version !== "3.0") {
|
|
117
|
+
throw new RangeError(
|
|
118
|
+
`Invalid XSLT version "${version}": expected "2.0" or "3.0"`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return loadXslt3().then(() => undefined);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The underlying XSLT engine (advanced usage).
|
|
126
|
+
*
|
|
127
|
+
* The engine is created lazily by {@link XSLTProcessor#importStylesheet},
|
|
128
|
+
* so this getter returns `null` until a stylesheet has been imported.
|
|
129
|
+
* Prefer the public {@link XSLTProcessor#setStylesheetLoader} over reaching
|
|
130
|
+
* into the engine directly. A stylesheet run by @tradik/xslt3
|
|
131
|
+
* (`xsltVersion: "auto"`) has an `Xslt3Engine` (bridge/engine.js).
|
|
132
|
+
*
|
|
133
|
+
* @returns {import('./xslt/engine.js').XsltEngine|import('./bridge/engine.js').Xslt3Engine|null}
|
|
134
|
+
* The engine, or null before import
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* processor.importStylesheet(xslDoc, '/styles/main.xsl');
|
|
138
|
+
* console.log(processor.engine.outputSettings.method);
|
|
139
|
+
*/
|
|
140
|
+
get engine() {
|
|
141
|
+
return this._engine;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Sets the loader used to resolve `xsl:import` and `xsl:include` references.
|
|
146
|
+
*
|
|
147
|
+
* The loader is synchronous: it MUST return the external stylesheet as a
|
|
148
|
+
* `Document` or as an XML string (which is parsed automatically). Promises
|
|
149
|
+
* are not awaited by the engine, so pre-load remote stylesheets before
|
|
150
|
+
* calling `importStylesheet`.
|
|
151
|
+
*
|
|
152
|
+
* The loader may be set before or after `importStylesheet`. When set before,
|
|
153
|
+
* it is passed to the engine on creation, which is required for the loader to
|
|
154
|
+
* be used while the stylesheet is being compiled. When set after, the live
|
|
155
|
+
* engine is updated as well.
|
|
156
|
+
*
|
|
157
|
+
* @param {((href: string, baseUri?: string) => (Document|string))|null} loader
|
|
158
|
+
* The loader function, or null to remove a previously configured loader
|
|
159
|
+
* @returns {XSLTProcessor} This processor, to allow chaining
|
|
160
|
+
* @throws {TypeError} If the loader is neither a function nor null
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* processor.setStylesheetLoader((href) => readFileSync(href, 'utf8'));
|
|
164
|
+
* processor.importStylesheet(mainStylesheet, '/styles/main.xsl');
|
|
165
|
+
*/
|
|
166
|
+
setStylesheetLoader(loader) {
|
|
167
|
+
if (
|
|
168
|
+
loader !== null &&
|
|
169
|
+
loader !== undefined &&
|
|
170
|
+
typeof loader !== "function"
|
|
171
|
+
) {
|
|
172
|
+
throw new TypeError(
|
|
173
|
+
"Failed to execute 'setStylesheetLoader' on 'XSLTProcessor': The loader argument must be a function or null.",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
this._stylesheetLoader = loader ?? null;
|
|
178
|
+
|
|
179
|
+
// Keep an already created engine in sync
|
|
180
|
+
if (this._engine) {
|
|
181
|
+
this._engine.setStylesheetLoader(this._stylesheetLoader);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Sets the loader used to resolve the XSLT `document()` function.
|
|
189
|
+
*
|
|
190
|
+
* The loader is synchronous: it MUST return the referenced document as a
|
|
191
|
+
* `Document`, as an XML string (which is parsed automatically) or as `null`
|
|
192
|
+
* when the document cannot be provided. Returning `null`, like configuring no
|
|
193
|
+
* loader at all, makes `document()` evaluate to an empty node-set rather than
|
|
194
|
+
* failing the transformation.
|
|
195
|
+
*
|
|
196
|
+
* The loader may be set before or after `importStylesheet`; a live engine is
|
|
197
|
+
* kept in sync.
|
|
198
|
+
*
|
|
199
|
+
* @param {((uri: string, baseUri?: string) => (Document|string|null))|null} loader
|
|
200
|
+
* The loader function, or null to remove a previously configured loader
|
|
201
|
+
* @returns {XSLTProcessor} This processor, to allow chaining
|
|
202
|
+
* @throws {TypeError} If the loader is neither a function nor null
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* // Node.js: resolve document() against the file system
|
|
206
|
+
* import { readFileSync } from 'node:fs';
|
|
207
|
+
* processor.setDocumentLoader((uri) => readFileSync(uri, 'utf8'));
|
|
208
|
+
* processor.importStylesheet(xslDoc, '/styles/main.xsl');
|
|
209
|
+
*/
|
|
210
|
+
setDocumentLoader(loader) {
|
|
211
|
+
if (
|
|
212
|
+
loader !== null &&
|
|
213
|
+
loader !== undefined &&
|
|
214
|
+
typeof loader !== "function"
|
|
215
|
+
) {
|
|
216
|
+
throw new TypeError(
|
|
217
|
+
"Failed to execute 'setDocumentLoader' on 'XSLTProcessor': The loader argument must be a function or null.",
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
this._documentLoader = loader ?? null;
|
|
222
|
+
|
|
223
|
+
// Keep an already created engine in sync
|
|
224
|
+
if (this._engine) {
|
|
225
|
+
this._engine.setDocumentLoader(this._engineDocumentLoader());
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return this;
|
|
33
229
|
}
|
|
34
230
|
|
|
35
231
|
/**
|
|
@@ -40,14 +236,21 @@ export class XSLTProcessor {
|
|
|
40
236
|
* <xsl:stylesheet> or <xsl:transform> element.
|
|
41
237
|
*
|
|
42
238
|
* @param {Node} style - The XSLT stylesheet to import (Document or Element)
|
|
239
|
+
* @param {string} [stylesheetUri] - Optional URI of the stylesheet, used as the
|
|
240
|
+
* base URI when resolving relative `xsl:import`/`xsl:include` hrefs. When
|
|
241
|
+
* omitted, hrefs are passed to the loader unresolved.
|
|
43
242
|
* @returns {void}
|
|
243
|
+
* @throws {Error} When the stylesheet is malformed or invalid, e.g. has an
|
|
244
|
+
* invalid pattern (XSLT 1.0 section 5.2), or, with `xsltVersion: "auto"`,
|
|
245
|
+
* is an XSLT 2.0/3.0 stylesheet and @tradik/xslt3 has not been loaded
|
|
246
|
+
* ({@link XSLTProcessor.preload})
|
|
44
247
|
*
|
|
45
248
|
* @example
|
|
46
249
|
* const parser = new DOMParser();
|
|
47
250
|
* const xslDoc = parser.parseFromString(xslText, 'application/xml');
|
|
48
|
-
* processor.importStylesheet(xslDoc);
|
|
251
|
+
* processor.importStylesheet(xslDoc, '/styles/main.xsl');
|
|
49
252
|
*/
|
|
50
|
-
importStylesheet(style) {
|
|
253
|
+
importStylesheet(style, stylesheetUri) {
|
|
51
254
|
if (!style) {
|
|
52
255
|
throw new TypeError(
|
|
53
256
|
"Failed to execute 'importStylesheet' on 'XSLTProcessor': 1 argument required, but only 0 present.",
|
|
@@ -62,28 +265,193 @@ export class XSLTProcessor {
|
|
|
62
265
|
}
|
|
63
266
|
|
|
64
267
|
// Check for parser errors
|
|
65
|
-
|
|
66
|
-
? style.querySelector("parsererror")
|
|
67
|
-
: null;
|
|
68
|
-
if (errorNode) {
|
|
268
|
+
if (findParseError(style)) {
|
|
69
269
|
throw new Error("XSLT stylesheet contains parse errors");
|
|
70
270
|
}
|
|
71
271
|
|
|
72
|
-
this.
|
|
73
|
-
|
|
272
|
+
this._compile(style, stylesheetUri);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Imports a stylesheet whose `xsl:import`/`xsl:include` modules and
|
|
277
|
+
* literal `document('...')` documents are loaded asynchronously first
|
|
278
|
+
* (non-W3C). The modules are loaded in parallel, each URI once; a cycle
|
|
279
|
+
* rejects with "Circular stylesheet reference detected". A document that
|
|
280
|
+
* fails to load is reported only if the transformation evaluates that
|
|
281
|
+
* `document()` call. Computed `document()` URIs still go through the
|
|
282
|
+
* synchronous {@link XSLTProcessor#setDocumentLoader} loader, and modules
|
|
283
|
+
* that were not preloaded through {@link XSLTProcessor#setStylesheetLoader}.
|
|
284
|
+
*
|
|
285
|
+
* @param {Node|string|Uint8Array|ArrayBuffer|ReadableStream|AsyncIterable} style -
|
|
286
|
+
* The stylesheet: a node, or markup / a stream of markup to parse
|
|
287
|
+
* @param {string} [stylesheetUri] - Base URI of relative hrefs and
|
|
288
|
+
* `document()` URIs
|
|
289
|
+
* @param {object} [options] - Loading options
|
|
290
|
+
* @param {Function} [options.loader] - `(uri, baseUri, { signal }) =>
|
|
291
|
+
* Promise<Document|string|Uint8Array|ArrayBuffer|Response|null>`; the
|
|
292
|
+
* global `fetch` by default
|
|
293
|
+
* @param {Function} [options.documentLoader] - Loader of `document()`
|
|
294
|
+
* documents, `options.loader` by default
|
|
295
|
+
* @param {AbortSignal} [options.signal] - Cancels loading
|
|
296
|
+
* @returns {Promise<void>} Resolves once the stylesheet is imported; on
|
|
297
|
+
* failure the processor keeps its previous stylesheet
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* await processor.importStylesheetAsync(xslDoc, "https://example.com/xsl/main.xsl");
|
|
301
|
+
*/
|
|
302
|
+
importStylesheetAsync(style, stylesheetUri, options = {}) {
|
|
303
|
+
return importStylesheetAsync(this, style, stylesheetUri, options);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Compile a stylesheet into a new engine; the processor is only updated
|
|
308
|
+
* when compiling succeeds.
|
|
309
|
+
*
|
|
310
|
+
* @param {Node} style - The stylesheet
|
|
311
|
+
* @param {string} [stylesheetUri] - Its URI
|
|
312
|
+
* @param {object} [preloaded] - What importStylesheetAsync loaded
|
|
313
|
+
* @param {Function|null} [preloaded.stylesheetLoader] - Module loader
|
|
314
|
+
* @param {Node[]} [preloaded.modules] - Every stylesheet module
|
|
315
|
+
* @param {Map<string, object>|null} [preloaded.documents] - document() documents
|
|
316
|
+
* @returns {void}
|
|
317
|
+
* @private
|
|
318
|
+
*/
|
|
319
|
+
_compile(style, stylesheetUri, preloaded = {}) {
|
|
320
|
+
const {
|
|
321
|
+
stylesheetLoader = this._stylesheetLoader,
|
|
322
|
+
modules = [style],
|
|
323
|
+
documents = null,
|
|
324
|
+
} = preloaded;
|
|
325
|
+
const engineOptions = {
|
|
326
|
+
legacyNameTests: this._options.legacyNameTests,
|
|
327
|
+
enableDynamicEvaluate: this._options.enableDynamicEvaluate,
|
|
328
|
+
clock: this._options.clock,
|
|
329
|
+
maxTemplateDepth: this._options.maxTemplateDepth,
|
|
330
|
+
stylesheetLoader,
|
|
331
|
+
documentLoader: this._engineDocumentLoader(documents),
|
|
332
|
+
};
|
|
333
|
+
const engine = this._usesXslt3(style)
|
|
334
|
+
? createXslt3Engine(style, engineOptions)
|
|
335
|
+
: new XsltEngine(engineOptions);
|
|
74
336
|
|
|
75
337
|
// Apply any previously set parameters
|
|
76
338
|
for (const [key, value] of this._parameters) {
|
|
77
|
-
|
|
339
|
+
engine.setParameterValue(key, value);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// An invalid stylesheet (e.g. an invalid pattern) throws here and leaves
|
|
343
|
+
// the processor as it was
|
|
344
|
+
engine.importStylesheet(style, stylesheetUri);
|
|
345
|
+
// Modules not preloaded keep going through the configured loader
|
|
346
|
+
engine.setStylesheetLoader(this._stylesheetLoader);
|
|
347
|
+
this._engine = engine;
|
|
348
|
+
this._stylesheet = style;
|
|
349
|
+
this._stylesheetUri = stylesheetUri;
|
|
350
|
+
this._modules = modules;
|
|
351
|
+
this._preloadedDocuments = documents;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Whether a stylesheet is run by @tradik/xslt3 (`xsltVersion: "auto"` and
|
|
356
|
+
* version 2.0 or more).
|
|
357
|
+
*
|
|
358
|
+
* @param {Node} style - The stylesheet
|
|
359
|
+
* @returns {boolean} True for @tradik/xslt3
|
|
360
|
+
* @private
|
|
361
|
+
*/
|
|
362
|
+
_usesXslt3(style) {
|
|
363
|
+
return usesXslt3(style, this._options.xsltVersion);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Load the engine a stylesheet needs before compiling it (async API).
|
|
368
|
+
*
|
|
369
|
+
* @param {Node} style - The stylesheet
|
|
370
|
+
* @returns {Promise<void>} Resolves once the engine is available
|
|
371
|
+
* @private
|
|
372
|
+
*/
|
|
373
|
+
async _prepareEngine(style) {
|
|
374
|
+
if (this._usesXslt3(style)) await loadXslt3();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* The document() loader given to the engine: preloaded documents first,
|
|
379
|
+
* then the configured synchronous loader.
|
|
380
|
+
*
|
|
381
|
+
* @param {Map<string, object>|null} [documents] - Preloaded documents
|
|
382
|
+
* @returns {Function|null} The loader
|
|
383
|
+
* @private
|
|
384
|
+
*/
|
|
385
|
+
_engineDocumentLoader(documents = this._preloadedDocuments) {
|
|
386
|
+
return documents
|
|
387
|
+
? preloadedDocumentLoader(documents, this._documentLoader)
|
|
388
|
+
: this._documentLoader;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Add asynchronously preloaded document() documents.
|
|
393
|
+
*
|
|
394
|
+
* @param {Map<string, object>} documents - Preloaded outcomes by URI
|
|
395
|
+
* @returns {void}
|
|
396
|
+
* @private
|
|
397
|
+
*/
|
|
398
|
+
_usePreloadedDocuments(documents) {
|
|
399
|
+
this._preloadedDocuments = new Map([
|
|
400
|
+
...(this._preloadedDocuments ?? []),
|
|
401
|
+
...documents,
|
|
402
|
+
]);
|
|
403
|
+
this._engine.setDocumentLoader(this._engineDocumentLoader());
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Throw the native error of a transformation without stylesheet.
|
|
408
|
+
*
|
|
409
|
+
* @param {string} method - The method called
|
|
410
|
+
* @returns {void}
|
|
411
|
+
* @throws {Error} When no stylesheet has been imported
|
|
412
|
+
* @private
|
|
413
|
+
*/
|
|
414
|
+
_requireStylesheet(method) {
|
|
415
|
+
if (!this._engine || !this._stylesheet) {
|
|
416
|
+
throw new Error(
|
|
417
|
+
`Failed to execute '${method}' on 'XSLTProcessor': No stylesheet has been imported.`,
|
|
418
|
+
);
|
|
78
419
|
}
|
|
420
|
+
}
|
|
79
421
|
|
|
80
|
-
|
|
422
|
+
/**
|
|
423
|
+
* Throw the native error of a source that is not a document, element or
|
|
424
|
+
* fragment.
|
|
425
|
+
*
|
|
426
|
+
* @param {string} method - The method called
|
|
427
|
+
* @param {Node} source - The source node
|
|
428
|
+
* @returns {void}
|
|
429
|
+
* @throws {TypeError} For other node types
|
|
430
|
+
* @private
|
|
431
|
+
*/
|
|
432
|
+
_checkSource(method, source) {
|
|
433
|
+
if (
|
|
434
|
+
source.nodeType !== 1 &&
|
|
435
|
+
source.nodeType !== 9 &&
|
|
436
|
+
source.nodeType !== 11
|
|
437
|
+
) {
|
|
438
|
+
throw new TypeError(
|
|
439
|
+
`Failed to execute '${method}' on 'XSLTProcessor': The source is not a valid node type.`,
|
|
440
|
+
);
|
|
441
|
+
}
|
|
81
442
|
}
|
|
82
443
|
|
|
83
444
|
/**
|
|
84
445
|
* Transforms the node source by applying the XSLT stylesheet.
|
|
85
446
|
* Returns a document fragment.
|
|
86
447
|
*
|
|
448
|
+
* As in Chrome, when `output` is an HTML document and the output method is
|
|
449
|
+
* `html` (declared, or detected from an `<html>` result root), the result
|
|
450
|
+
* is serialized and parsed by the HTML parser of `output`, so it holds
|
|
451
|
+
* real `HTMLElement`s (`<a>` is an `HTMLAnchorElement`, `<script>` runs
|
|
452
|
+
* when inserted). Other results keep the element names and namespaces of
|
|
453
|
+
* the result tree.
|
|
454
|
+
*
|
|
87
455
|
* @param {Node} source - The XML document to transform
|
|
88
456
|
* @param {Document} output - The document that will own the generated fragment
|
|
89
457
|
* @returns {DocumentFragment} The transformed result as a DocumentFragment
|
|
@@ -105,22 +473,9 @@ export class XSLTProcessor {
|
|
|
105
473
|
);
|
|
106
474
|
}
|
|
107
475
|
|
|
108
|
-
|
|
109
|
-
throw new Error(
|
|
110
|
-
"Failed to execute 'transformToFragment' on 'XSLTProcessor': No stylesheet has been imported.",
|
|
111
|
-
);
|
|
112
|
-
}
|
|
476
|
+
this._requireStylesheet("transformToFragment");
|
|
113
477
|
|
|
114
|
-
|
|
115
|
-
if (
|
|
116
|
-
source.nodeType !== 1 &&
|
|
117
|
-
source.nodeType !== 9 &&
|
|
118
|
-
source.nodeType !== 11
|
|
119
|
-
) {
|
|
120
|
-
throw new TypeError(
|
|
121
|
-
"Failed to execute 'transformToFragment' on 'XSLTProcessor': The source is not a valid node type.",
|
|
122
|
-
);
|
|
123
|
-
}
|
|
478
|
+
this._checkSource("transformToFragment", source);
|
|
124
479
|
|
|
125
480
|
// Validate output document
|
|
126
481
|
if (output.nodeType !== 9) {
|
|
@@ -130,7 +485,7 @@ export class XSLTProcessor {
|
|
|
130
485
|
}
|
|
131
486
|
|
|
132
487
|
try {
|
|
133
|
-
return this._engine.
|
|
488
|
+
return this._engine.transformToFragment(source, output);
|
|
134
489
|
} catch (error) {
|
|
135
490
|
// Match native behavior - return null on error
|
|
136
491
|
console.error("XSLT transformation error:", error);
|
|
@@ -156,32 +511,106 @@ export class XSLTProcessor {
|
|
|
156
511
|
);
|
|
157
512
|
}
|
|
158
513
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
514
|
+
this._requireStylesheet("transformToDocument");
|
|
515
|
+
|
|
516
|
+
this._checkSource("transformToDocument", source);
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
return this._engine.transformToDocument(source);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
// Match native behavior - return null on error
|
|
522
|
+
console.error("XSLT transformation error:", error);
|
|
523
|
+
return null;
|
|
163
524
|
}
|
|
525
|
+
}
|
|
164
526
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
527
|
+
/**
|
|
528
|
+
* Transforms the node source by applying the XSLT stylesheet and serializes
|
|
529
|
+
* the result to a string honoring the stylesheet `xsl:output` settings.
|
|
530
|
+
*
|
|
531
|
+
* Non-W3C convenience method: the native XSLTProcessor has no equivalent.
|
|
532
|
+
* Output method, indentation, XML declaration, document type declaration,
|
|
533
|
+
* CDATA sections and `disable-output-escaping` are all honored
|
|
534
|
+
* (XSLT 1.0 section 16).
|
|
535
|
+
*
|
|
536
|
+
* @param {Node} source - The XML document to transform
|
|
537
|
+
* @returns {string|null} The serialized result, or null on a transformation error
|
|
538
|
+
*
|
|
539
|
+
* @example
|
|
540
|
+
* const xml = processor.transformToString(xmlDoc);
|
|
541
|
+
* // '<?xml version="1.0" encoding="UTF-8"?>\n<BAR>\n <QUX/>\n</BAR>'
|
|
542
|
+
*/
|
|
543
|
+
transformToString(source) {
|
|
544
|
+
if (!source) {
|
|
171
545
|
throw new TypeError(
|
|
172
|
-
"Failed to execute '
|
|
546
|
+
"Failed to execute 'transformToString' on 'XSLTProcessor': 1 argument required, but only 0 present.",
|
|
173
547
|
);
|
|
174
548
|
}
|
|
175
549
|
|
|
550
|
+
this._requireStylesheet("transformToString");
|
|
551
|
+
|
|
552
|
+
this._checkSource("transformToString", source);
|
|
553
|
+
|
|
176
554
|
try {
|
|
177
|
-
return this._engine.
|
|
555
|
+
return this._engine.transformToString(source);
|
|
178
556
|
} catch (error) {
|
|
179
|
-
// Match
|
|
557
|
+
// Match transformToDocument behavior - return null on error
|
|
180
558
|
console.error("XSLT transformation error:", error);
|
|
181
559
|
return null;
|
|
182
560
|
}
|
|
183
561
|
}
|
|
184
562
|
|
|
563
|
+
/**
|
|
564
|
+
* Transforms asynchronously and resolves with the serialized result
|
|
565
|
+
* (non-W3C). The source may be a node, markup, bytes, a `ReadableStream`
|
|
566
|
+
* or an async iterable of strings or bytes; streams are read to their end
|
|
567
|
+
* before parsing, because XSLT 1.0 needs the whole source tree. Unlike
|
|
568
|
+
* {@link XSLTProcessor#transformToString}, failures reject the promise.
|
|
569
|
+
*
|
|
570
|
+
* @param {Node|string|Uint8Array|ArrayBuffer|ReadableStream|AsyncIterable} source - Input
|
|
571
|
+
* @param {object} [options] - Options
|
|
572
|
+
* @param {AbortSignal} [options.signal] - Cancels loading and reading
|
|
573
|
+
* @param {Node|string|ReadableStream|AsyncIterable} [options.stylesheet] -
|
|
574
|
+
* A stylesheet to import first with importStylesheetAsync
|
|
575
|
+
* @param {string} [options.stylesheetUri] - The URI of that stylesheet
|
|
576
|
+
* @param {Function} [options.fetchStylesheet] - Asynchronous loader of its
|
|
577
|
+
* xsl:import/xsl:include modules (`fetch` by default)
|
|
578
|
+
* @param {Function} [options.fetchDocument] - Asynchronous loader of the
|
|
579
|
+
* literal document() documents (`fetchStylesheet` by default when a
|
|
580
|
+
* stylesheet is given)
|
|
581
|
+
* @returns {Promise<string>} The serialized result
|
|
582
|
+
*
|
|
583
|
+
* @example
|
|
584
|
+
* const html = await processor.transformAsync((await fetch("data.xml")).body);
|
|
585
|
+
*/
|
|
586
|
+
transformAsync(source, options = {}) {
|
|
587
|
+
return transformAsync(this, source, options);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Transforms and returns the serialized result as a `ReadableStream` of
|
|
592
|
+
* strings (non-W3C). The result tree is built in memory on the first read;
|
|
593
|
+
* serialization then produces chunks of about `chunkSize` code units on
|
|
594
|
+
* demand, so the output is never one string and the first bytes are
|
|
595
|
+
* available before serialization ends. Failures error the stream.
|
|
596
|
+
*
|
|
597
|
+
* @param {Node|string|Uint8Array|ArrayBuffer|ReadableStream|AsyncIterable} source - Input
|
|
598
|
+
* @param {{signal?: AbortSignal, chunkSize?: number}} [options] - `signal`
|
|
599
|
+
* cancels (errors the stream with its reason); `chunkSize` defaults to
|
|
600
|
+
* 16384
|
|
601
|
+
* @returns {ReadableStream<string>} The serialized result
|
|
602
|
+
* @throws {Error} When no stylesheet has been imported
|
|
603
|
+
* @throws {TypeError} For a source node of the wrong type
|
|
604
|
+
* @throws {RangeError} For an invalid chunk size
|
|
605
|
+
*
|
|
606
|
+
* @example
|
|
607
|
+
* // Node.js
|
|
608
|
+
* Readable.fromWeb(processor.transformToStream(xmlDoc)).pipe(process.stdout);
|
|
609
|
+
*/
|
|
610
|
+
transformToStream(source, options = {}) {
|
|
611
|
+
return transformToStream(this, source, options);
|
|
612
|
+
}
|
|
613
|
+
|
|
185
614
|
/**
|
|
186
615
|
* Sets a parameter in the XSLT stylesheet.
|
|
187
616
|
*
|
|
@@ -212,7 +641,7 @@ export class XSLTProcessor {
|
|
|
212
641
|
|
|
213
642
|
// If engine is already initialized, update it
|
|
214
643
|
if (this._engine) {
|
|
215
|
-
this._engine.
|
|
644
|
+
this._engine.setParameterValue(key, value);
|
|
216
645
|
}
|
|
217
646
|
}
|
|
218
647
|
|
|
@@ -279,7 +708,7 @@ export class XSLTProcessor {
|
|
|
279
708
|
this._parameters.delete(key);
|
|
280
709
|
|
|
281
710
|
if (this._engine) {
|
|
282
|
-
|
|
711
|
+
this._engine.clearParameterValue(key);
|
|
283
712
|
}
|
|
284
713
|
}
|
|
285
714
|
|
|
@@ -297,13 +726,20 @@ export class XSLTProcessor {
|
|
|
297
726
|
this._parameters.clear();
|
|
298
727
|
|
|
299
728
|
if (this._engine) {
|
|
300
|
-
this._engine.
|
|
729
|
+
this._engine.clearParameterValues();
|
|
301
730
|
}
|
|
302
731
|
}
|
|
303
732
|
|
|
304
733
|
/**
|
|
305
734
|
* Removes all parameters and stylesheets from the XSLTProcessor.
|
|
306
735
|
*
|
|
736
|
+
* Per the W3C `XSLTProcessor` semantics, `reset()` clears stylesheet state and
|
|
737
|
+
* parameters only. The stylesheet and document loaders are processor
|
|
738
|
+
* configuration rather than stylesheet state, so they are deliberately
|
|
739
|
+
* preserved and stay effective for the next `importStylesheet()` call. Pass
|
|
740
|
+
* `null` to {@link XSLTProcessor#setStylesheetLoader} or
|
|
741
|
+
* {@link XSLTProcessor#setDocumentLoader} to remove them explicitly.
|
|
742
|
+
*
|
|
307
743
|
* @returns {void}
|
|
308
744
|
*
|
|
309
745
|
* @example
|
|
@@ -313,22 +749,28 @@ export class XSLTProcessor {
|
|
|
313
749
|
reset() {
|
|
314
750
|
this._engine = null;
|
|
315
751
|
this._stylesheet = null;
|
|
752
|
+
this._stylesheetUri = undefined;
|
|
753
|
+
this._modules = [];
|
|
754
|
+
this._preloadedDocuments = null;
|
|
316
755
|
this._parameters.clear();
|
|
317
756
|
}
|
|
318
757
|
}
|
|
319
758
|
|
|
320
759
|
/**
|
|
321
|
-
* Check if native XSLTProcessor is available and functional
|
|
760
|
+
* Check if native XSLTProcessor is available and functional.
|
|
761
|
+
*
|
|
762
|
+
* After installGlobal() replaced the global, the original native constructor
|
|
763
|
+
* is probed; this implementation never counts as native.
|
|
322
764
|
*
|
|
323
765
|
* @returns {boolean} True if native XSLTProcessor works correctly
|
|
324
766
|
*/
|
|
325
767
|
export function isNativeXSLTSupported() {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
768
|
+
const current = globalThis.XSLTProcessor;
|
|
769
|
+
const Native = current === XSLTProcessor ? nativeProcessor : current;
|
|
770
|
+
if (typeof Native !== "function") return false;
|
|
329
771
|
|
|
330
772
|
try {
|
|
331
|
-
const processor = new
|
|
773
|
+
const processor = new Native();
|
|
332
774
|
const parser = new DOMParser();
|
|
333
775
|
|
|
334
776
|
const xslt = parser.parseFromString(
|
|
@@ -361,6 +803,10 @@ export function installGlobal(force = false) {
|
|
|
361
803
|
return false;
|
|
362
804
|
}
|
|
363
805
|
|
|
806
|
+
const current = globalThis.XSLTProcessor;
|
|
807
|
+
if (typeof current === "function" && current !== XSLTProcessor) {
|
|
808
|
+
nativeProcessor = current;
|
|
809
|
+
}
|
|
364
810
|
globalThis.XSLTProcessor = XSLTProcessor;
|
|
365
811
|
return true;
|
|
366
812
|
}
|