@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/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradik/xslt-processor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "JavaScript implementation of XSLTProcessor for browser environments and CLI",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"workspaces": [
|
|
7
|
+
"packages/*"
|
|
8
|
+
],
|
|
6
9
|
"main": "dist/xslt-processor.cjs",
|
|
7
10
|
"module": "dist/xslt-processor.js",
|
|
8
11
|
"browser": "dist/xslt-processor.js",
|
|
@@ -38,13 +41,31 @@
|
|
|
38
41
|
},
|
|
39
42
|
"scripts": {
|
|
40
43
|
"build": "node scripts/build.js",
|
|
41
|
-
"test": "node --test --experimental-test-coverage
|
|
44
|
+
"test": "node --test --experimental-test-coverage --test-coverage-lines=100 --test-coverage-functions=100 --test-coverage-branches=96 --test-coverage-exclude=\"**/*.test.js\" --test-coverage-exclude=\"packages/**\" \"src/**/*.test.js\"",
|
|
45
|
+
"test:xslt3": "npm test --workspace @tradik/xslt3",
|
|
46
|
+
"suites:fetch": "node packages/xslt3/test-suites/fetch.mjs all",
|
|
47
|
+
"test:suites:unit": "node --test --experimental-test-coverage --test-coverage-include=\"packages/xslt3/test-suites/**\" --test-coverage-exclude=\"**/*.test.mjs\" --test-coverage-lines=96 \"packages/xslt3/test-suites/**/*.test.mjs\"",
|
|
48
|
+
"test:qt3:parse": "node packages/xslt3/test-suites/run.mjs qt3 --parse-only",
|
|
49
|
+
"test:qt3": "node packages/xslt3/test-suites/run.mjs qt3",
|
|
50
|
+
"test:xslt30": "node packages/xslt3/test-suites/run.mjs xslt30",
|
|
42
51
|
"test:watch": "node --test --watch src/**/*.test.js",
|
|
43
|
-
"test:
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
52
|
+
"test:conformance": "node scripts/conformance.mjs",
|
|
53
|
+
"test:dom": "node scripts/test-dom.mjs",
|
|
54
|
+
"test:conformance:unit": "node --test \"scripts/conformance/*.test.mjs\"",
|
|
55
|
+
"conformance:fetch": "node scripts/fetch-conformance.mjs",
|
|
56
|
+
"lint": "eslint src/ bin/ scripts/ tests/browser/ site/ packages/",
|
|
57
|
+
"format": "prettier --write \"src/**/*.js\" \"bin/**/*.js\" \"scripts/**/*.{js,mjs}\" \"tests/browser/**/*.mjs\" \"site/scripts/*.mjs\" \"site/templates/**/js/*.js\" \"packages/*/src/**/*.js\" \"packages/*/test-suites/**/*.mjs\"",
|
|
58
|
+
"format:check": "prettier --check \"src/**/*.js\" \"bin/**/*.js\" \"scripts/**/*.{js,mjs}\" \"tests/browser/**/*.mjs\" \"site/scripts/*.mjs\" \"site/templates/**/js/*.js\" \"packages/*/src/**/*.js\" \"packages/*/test-suites/**/*.mjs\"",
|
|
59
|
+
"prepublishOnly": "npm run build && npm run test",
|
|
60
|
+
"docs:check": "node scripts/check-links.mjs",
|
|
61
|
+
"bench": "node scripts/benchmark/run.mjs",
|
|
62
|
+
"bench:xpath": "node scripts/benchmark/run.mjs --suite xpath",
|
|
63
|
+
"bench:xslt": "node scripts/benchmark/run.mjs --suite xslt",
|
|
64
|
+
"test:bench": "node --test \"scripts/benchmark/*.test.mjs\"",
|
|
65
|
+
"test:browser": "playwright test -c tests/browser/playwright.config.mjs",
|
|
66
|
+
"test:binaries": "node --test \"scripts/binaries/*.test.mjs\" \"scripts/lib/*.test.mjs\"",
|
|
67
|
+
"test:site": "node --test --experimental-test-coverage --test-coverage-include=\"site/**\" --test-coverage-lines=96 \"site/scripts/*.test.mjs\"",
|
|
68
|
+
"site:content": "node site/scripts/build-content.mjs"
|
|
48
69
|
},
|
|
49
70
|
"keywords": [
|
|
50
71
|
"xslt",
|
|
@@ -65,15 +86,34 @@
|
|
|
65
86
|
"bugs": {
|
|
66
87
|
"url": "https://github.com/spagu/XSLT-Processor/issues"
|
|
67
88
|
},
|
|
68
|
-
"homepage": "https://
|
|
89
|
+
"homepage": "https://xslt-processor.tradik.com/",
|
|
69
90
|
"engines": {
|
|
70
91
|
"node": ">=20.19.0"
|
|
71
92
|
},
|
|
72
93
|
"devDependencies": {
|
|
73
94
|
"@eslint/js": "^10.0.1",
|
|
95
|
+
"@playwright/test": "^1.63.0",
|
|
96
|
+
"@xmldom/xmldom": "^0.9.12",
|
|
74
97
|
"esbuild": "^0.28.2",
|
|
75
|
-
"eslint": "^10.
|
|
76
|
-
"jsdom": "^
|
|
77
|
-
"
|
|
98
|
+
"eslint": "^10.11.0",
|
|
99
|
+
"jsdom": "^30.1.1",
|
|
100
|
+
"linkedom": "^0.18.13",
|
|
101
|
+
"prettier": "^3.9.9"
|
|
102
|
+
},
|
|
103
|
+
"peerDependencies": {
|
|
104
|
+
"@tradik/xslt3": "^1.0.0",
|
|
105
|
+
"@xmldom/xmldom": ">=0.9.0",
|
|
106
|
+
"jsdom": ">=25.0.0"
|
|
107
|
+
},
|
|
108
|
+
"peerDependenciesMeta": {
|
|
109
|
+
"@tradik/xslt3": {
|
|
110
|
+
"optional": true
|
|
111
|
+
},
|
|
112
|
+
"@xmldom/xmldom": {
|
|
113
|
+
"optional": true
|
|
114
|
+
},
|
|
115
|
+
"jsdom": {
|
|
116
|
+
"optional": true
|
|
117
|
+
}
|
|
78
118
|
}
|
|
79
119
|
}
|
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,12 +46,79 @@ 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();
|
|
33
90
|
this._stylesheetLoader = null;
|
|
34
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);
|
|
35
122
|
}
|
|
36
123
|
|
|
37
124
|
/**
|
|
@@ -40,9 +127,11 @@ export class XSLTProcessor {
|
|
|
40
127
|
* The engine is created lazily by {@link XSLTProcessor#importStylesheet},
|
|
41
128
|
* so this getter returns `null` until a stylesheet has been imported.
|
|
42
129
|
* Prefer the public {@link XSLTProcessor#setStylesheetLoader} over reaching
|
|
43
|
-
* into the engine directly.
|
|
130
|
+
* into the engine directly. A stylesheet run by @tradik/xslt3
|
|
131
|
+
* (`xsltVersion: "auto"`) has an `Xslt3Engine` (bridge/engine.js).
|
|
44
132
|
*
|
|
45
|
-
* @returns {import('./xslt/engine.js').XsltEngine|null}
|
|
133
|
+
* @returns {import('./xslt/engine.js').XsltEngine|import('./bridge/engine.js').Xslt3Engine|null}
|
|
134
|
+
* The engine, or null before import
|
|
46
135
|
*
|
|
47
136
|
* @example
|
|
48
137
|
* processor.importStylesheet(xslDoc, '/styles/main.xsl');
|
|
@@ -133,7 +222,7 @@ export class XSLTProcessor {
|
|
|
133
222
|
|
|
134
223
|
// Keep an already created engine in sync
|
|
135
224
|
if (this._engine) {
|
|
136
|
-
this._engine.setDocumentLoader(this.
|
|
225
|
+
this._engine.setDocumentLoader(this._engineDocumentLoader());
|
|
137
226
|
}
|
|
138
227
|
|
|
139
228
|
return this;
|
|
@@ -151,6 +240,10 @@ export class XSLTProcessor {
|
|
|
151
240
|
* base URI when resolving relative `xsl:import`/`xsl:include` hrefs. When
|
|
152
241
|
* omitted, hrefs are passed to the loader unresolved.
|
|
153
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})
|
|
154
247
|
*
|
|
155
248
|
* @example
|
|
156
249
|
* const parser = new DOMParser();
|
|
@@ -172,31 +265,193 @@ export class XSLTProcessor {
|
|
|
172
265
|
}
|
|
173
266
|
|
|
174
267
|
// Check for parser errors
|
|
175
|
-
|
|
176
|
-
? style.querySelector("parsererror")
|
|
177
|
-
: null;
|
|
178
|
-
if (errorNode) {
|
|
268
|
+
if (findParseError(style)) {
|
|
179
269
|
throw new Error("XSLT stylesheet contains parse errors");
|
|
180
270
|
}
|
|
181
271
|
|
|
182
|
-
this.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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);
|
|
187
336
|
|
|
188
337
|
// Apply any previously set parameters
|
|
189
338
|
for (const [key, value] of this._parameters) {
|
|
190
|
-
|
|
339
|
+
engine.setParameterValue(key, value);
|
|
191
340
|
}
|
|
192
341
|
|
|
193
|
-
|
|
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
|
+
);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
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
|
+
}
|
|
194
442
|
}
|
|
195
443
|
|
|
196
444
|
/**
|
|
197
445
|
* Transforms the node source by applying the XSLT stylesheet.
|
|
198
446
|
* Returns a document fragment.
|
|
199
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
|
+
*
|
|
200
455
|
* @param {Node} source - The XML document to transform
|
|
201
456
|
* @param {Document} output - The document that will own the generated fragment
|
|
202
457
|
* @returns {DocumentFragment} The transformed result as a DocumentFragment
|
|
@@ -218,22 +473,9 @@ export class XSLTProcessor {
|
|
|
218
473
|
);
|
|
219
474
|
}
|
|
220
475
|
|
|
221
|
-
|
|
222
|
-
throw new Error(
|
|
223
|
-
"Failed to execute 'transformToFragment' on 'XSLTProcessor': No stylesheet has been imported.",
|
|
224
|
-
);
|
|
225
|
-
}
|
|
476
|
+
this._requireStylesheet("transformToFragment");
|
|
226
477
|
|
|
227
|
-
|
|
228
|
-
if (
|
|
229
|
-
source.nodeType !== 1 &&
|
|
230
|
-
source.nodeType !== 9 &&
|
|
231
|
-
source.nodeType !== 11
|
|
232
|
-
) {
|
|
233
|
-
throw new TypeError(
|
|
234
|
-
"Failed to execute 'transformToFragment' on 'XSLTProcessor': The source is not a valid node type.",
|
|
235
|
-
);
|
|
236
|
-
}
|
|
478
|
+
this._checkSource("transformToFragment", source);
|
|
237
479
|
|
|
238
480
|
// Validate output document
|
|
239
481
|
if (output.nodeType !== 9) {
|
|
@@ -243,7 +485,7 @@ export class XSLTProcessor {
|
|
|
243
485
|
}
|
|
244
486
|
|
|
245
487
|
try {
|
|
246
|
-
return this._engine.
|
|
488
|
+
return this._engine.transformToFragment(source, output);
|
|
247
489
|
} catch (error) {
|
|
248
490
|
// Match native behavior - return null on error
|
|
249
491
|
console.error("XSLT transformation error:", error);
|
|
@@ -269,22 +511,9 @@ export class XSLTProcessor {
|
|
|
269
511
|
);
|
|
270
512
|
}
|
|
271
513
|
|
|
272
|
-
|
|
273
|
-
throw new Error(
|
|
274
|
-
"Failed to execute 'transformToDocument' on 'XSLTProcessor': No stylesheet has been imported.",
|
|
275
|
-
);
|
|
276
|
-
}
|
|
514
|
+
this._requireStylesheet("transformToDocument");
|
|
277
515
|
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
source.nodeType !== 1 &&
|
|
281
|
-
source.nodeType !== 9 &&
|
|
282
|
-
source.nodeType !== 11
|
|
283
|
-
) {
|
|
284
|
-
throw new TypeError(
|
|
285
|
-
"Failed to execute 'transformToDocument' on 'XSLTProcessor': The source is not a valid node type.",
|
|
286
|
-
);
|
|
287
|
-
}
|
|
516
|
+
this._checkSource("transformToDocument", source);
|
|
288
517
|
|
|
289
518
|
try {
|
|
290
519
|
return this._engine.transformToDocument(source);
|
|
@@ -318,22 +547,9 @@ export class XSLTProcessor {
|
|
|
318
547
|
);
|
|
319
548
|
}
|
|
320
549
|
|
|
321
|
-
|
|
322
|
-
throw new Error(
|
|
323
|
-
"Failed to execute 'transformToString' on 'XSLTProcessor': No stylesheet has been imported.",
|
|
324
|
-
);
|
|
325
|
-
}
|
|
550
|
+
this._requireStylesheet("transformToString");
|
|
326
551
|
|
|
327
|
-
|
|
328
|
-
if (
|
|
329
|
-
source.nodeType !== 1 &&
|
|
330
|
-
source.nodeType !== 9 &&
|
|
331
|
-
source.nodeType !== 11
|
|
332
|
-
) {
|
|
333
|
-
throw new TypeError(
|
|
334
|
-
"Failed to execute 'transformToString' on 'XSLTProcessor': The source is not a valid node type.",
|
|
335
|
-
);
|
|
336
|
-
}
|
|
552
|
+
this._checkSource("transformToString", source);
|
|
337
553
|
|
|
338
554
|
try {
|
|
339
555
|
return this._engine.transformToString(source);
|
|
@@ -344,6 +560,57 @@ export class XSLTProcessor {
|
|
|
344
560
|
}
|
|
345
561
|
}
|
|
346
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
|
+
|
|
347
614
|
/**
|
|
348
615
|
* Sets a parameter in the XSLT stylesheet.
|
|
349
616
|
*
|
|
@@ -482,22 +749,28 @@ export class XSLTProcessor {
|
|
|
482
749
|
reset() {
|
|
483
750
|
this._engine = null;
|
|
484
751
|
this._stylesheet = null;
|
|
752
|
+
this._stylesheetUri = undefined;
|
|
753
|
+
this._modules = [];
|
|
754
|
+
this._preloadedDocuments = null;
|
|
485
755
|
this._parameters.clear();
|
|
486
756
|
}
|
|
487
757
|
}
|
|
488
758
|
|
|
489
759
|
/**
|
|
490
|
-
* 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.
|
|
491
764
|
*
|
|
492
765
|
* @returns {boolean} True if native XSLTProcessor works correctly
|
|
493
766
|
*/
|
|
494
767
|
export function isNativeXSLTSupported() {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
768
|
+
const current = globalThis.XSLTProcessor;
|
|
769
|
+
const Native = current === XSLTProcessor ? nativeProcessor : current;
|
|
770
|
+
if (typeof Native !== "function") return false;
|
|
498
771
|
|
|
499
772
|
try {
|
|
500
|
-
const processor = new
|
|
773
|
+
const processor = new Native();
|
|
501
774
|
const parser = new DOMParser();
|
|
502
775
|
|
|
503
776
|
const xslt = parser.parseFromString(
|
|
@@ -530,6 +803,10 @@ export function installGlobal(force = false) {
|
|
|
530
803
|
return false;
|
|
531
804
|
}
|
|
532
805
|
|
|
806
|
+
const current = globalThis.XSLTProcessor;
|
|
807
|
+
if (typeof current === "function" && current !== XSLTProcessor) {
|
|
808
|
+
nativeProcessor = current;
|
|
809
|
+
}
|
|
533
810
|
globalThis.XSLTProcessor = XSLTProcessor;
|
|
534
811
|
return true;
|
|
535
812
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AbortSignal Helpers
|
|
3
|
+
*
|
|
4
|
+
* Every asynchronous entry point takes an optional `signal`. It is checked
|
|
5
|
+
* between steps (and between output chunks), passed to loaders, and raced
|
|
6
|
+
* against loader promises so that a loader ignoring the signal cannot keep an
|
|
7
|
+
* aborted call pending. The synchronous parts (compiling the stylesheet,
|
|
8
|
+
* building the result tree) cannot be interrupted once started.
|
|
9
|
+
*
|
|
10
|
+
* @module async/abort
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The reason of an aborted signal, with a DOMException fallback for
|
|
15
|
+
* implementations without `reason`.
|
|
16
|
+
*
|
|
17
|
+
* @param {AbortSignal} signal - An aborted signal
|
|
18
|
+
* @returns {*} The abort reason
|
|
19
|
+
*/
|
|
20
|
+
function abortReason(signal) {
|
|
21
|
+
return (
|
|
22
|
+
signal.reason ??
|
|
23
|
+
new globalThis.DOMException("This operation was aborted", "AbortError")
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Throw the abort reason when a signal is aborted.
|
|
29
|
+
*
|
|
30
|
+
* @param {AbortSignal} [signal] - Optional signal
|
|
31
|
+
* @returns {void}
|
|
32
|
+
* @throws {*} The abort reason
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* throwIfAborted(options.signal);
|
|
36
|
+
*/
|
|
37
|
+
export function throwIfAborted(signal) {
|
|
38
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Settle with a promise, or reject with the abort reason as soon as the
|
|
43
|
+
* signal aborts, whichever comes first.
|
|
44
|
+
*
|
|
45
|
+
* @template T
|
|
46
|
+
* @param {Promise<T>|T} work - The pending work
|
|
47
|
+
* @param {AbortSignal} [signal] - Optional signal
|
|
48
|
+
* @returns {Promise<T>} The work's outcome, or the abort rejection
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* const text = await abortable(loader(uri), signal);
|
|
52
|
+
*/
|
|
53
|
+
export function abortable(work, signal) {
|
|
54
|
+
if (!signal) return Promise.resolve(work);
|
|
55
|
+
if (signal.aborted) return Promise.reject(abortReason(signal));
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const onAbort = () => reject(abortReason(signal));
|
|
58
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
59
|
+
Promise.resolve(work)
|
|
60
|
+
.then(resolve, reject)
|
|
61
|
+
.finally(() => signal.removeEventListener("abort", onAbort));
|
|
62
|
+
});
|
|
63
|
+
}
|