@tradik/xslt-processor 1.0.3 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +290 -45
- package/bin/lib/options.js +114 -0
- package/bin/lib/paths.js +186 -0
- package/bin/lib/transform.js +115 -0
- package/bin/xslt.js +68 -162
- package/dist/xslt-processor.browser.js +2074 -159
- package/dist/xslt-processor.browser.js.map +4 -4
- package/dist/xslt-processor.browser.min.js +6 -2
- package/dist/xslt-processor.browser.min.js.map +4 -4
- package/dist/xslt-processor.cjs +2078 -158
- package/dist/xslt-processor.cjs.map +4 -4
- package/dist/xslt-processor.d.cts +299 -0
- package/dist/xslt-processor.d.ts +92 -4
- package/dist/xslt-processor.js +2073 -157
- package/dist/xslt-processor.js.map +4 -4
- package/package.json +26 -15
- package/src/XSLTProcessor.js +177 -8
- package/src/index.js +11 -5
- package/src/xpath/evaluator.js +48 -7
- package/src/xslt/elements.js +57 -0
- package/src/xslt/engine.js +471 -179
- package/src/xslt/formatNumber.js +220 -0
- package/src/xslt/functions.js +191 -0
- package/src/xslt/index.js +31 -0
- package/src/xslt/keys.js +141 -0
- package/src/xslt/literalResult.js +167 -0
- package/src/xslt/number.js +178 -0
- package/src/xslt/numberFormat.js +155 -0
- package/src/xslt/resultTree.js +74 -0
- package/src/xslt/serializer/baseWriter.js +283 -0
- package/src/xslt/serializer/constants.js +78 -0
- package/src/xslt/serializer/escape.js +98 -0
- package/src/xslt/serializer/htmlSerializer.js +141 -0
- package/src/xslt/serializer/indent.js +51 -0
- package/src/xslt/serializer/namespaces.js +68 -0
- package/src/xslt/serializer/rawText.js +41 -0
- package/src/xslt/serializer/settings.js +103 -0
- package/src/xslt/serializer/textSerializer.js +29 -0
- package/src/xslt/serializer/xmlSerializer.js +127 -0
- package/src/xslt/serializer.js +57 -0
- package/src/xslt/templatePriority.js +45 -0
- package/src/xslt/uri.js +68 -0
- package/src/xslt/whitespace.js +184 -0
- package/src/XSLTProcessor.test.js +0 -930
- package/src/xpath/evaluator.test.js +0 -1852
- package/src/xpath/tokenizer.test.js +0 -224
- package/src/xslt/engine.test.js +0 -3130
package/package.json
CHANGED
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradik/xslt-processor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "JavaScript implementation of XSLTProcessor for browser environments and CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/xslt-processor.cjs",
|
|
7
7
|
"module": "dist/xslt-processor.js",
|
|
8
|
-
"browser": "dist/xslt-processor.
|
|
8
|
+
"browser": "dist/xslt-processor.js",
|
|
9
9
|
"types": "dist/xslt-processor.d.ts",
|
|
10
10
|
"bin": {
|
|
11
|
-
"xslt": "
|
|
11
|
+
"xslt": "bin/xslt.js"
|
|
12
12
|
},
|
|
13
13
|
"exports": {
|
|
14
14
|
".": {
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
"browser": {
|
|
16
|
+
"types": "./dist/xslt-processor.d.ts",
|
|
17
|
+
"default": "./dist/xslt-processor.js"
|
|
18
|
+
},
|
|
19
|
+
"import": {
|
|
20
|
+
"types": "./dist/xslt-processor.d.ts",
|
|
21
|
+
"default": "./dist/xslt-processor.js"
|
|
22
|
+
},
|
|
23
|
+
"require": {
|
|
24
|
+
"types": "./dist/xslt-processor.d.cts",
|
|
25
|
+
"default": "./dist/xslt-processor.cjs"
|
|
26
|
+
}
|
|
19
27
|
}
|
|
20
28
|
},
|
|
21
29
|
"files": [
|
|
22
30
|
"dist",
|
|
23
31
|
"src",
|
|
24
|
-
"bin"
|
|
32
|
+
"bin",
|
|
33
|
+
"!src/**/*.test.js",
|
|
34
|
+
"!bin/**/*.test.js"
|
|
25
35
|
],
|
|
26
36
|
"publishConfig": {
|
|
27
37
|
"access": "public"
|
|
@@ -32,8 +42,8 @@
|
|
|
32
42
|
"test:watch": "node --test --watch src/**/*.test.js",
|
|
33
43
|
"test:browser": "node tests/browser-test-runner.js",
|
|
34
44
|
"lint": "eslint src/",
|
|
35
|
-
"format": "prettier --write \"src/**/*.js\"",
|
|
36
|
-
"format:check": "prettier --check \"src/**/*.js\"",
|
|
45
|
+
"format": "prettier --write \"src/**/*.js\" \"bin/**/*.js\"",
|
|
46
|
+
"format:check": "prettier --check \"src/**/*.js\" \"bin/**/*.js\"",
|
|
37
47
|
"prepublishOnly": "npm run build && npm run test"
|
|
38
48
|
},
|
|
39
49
|
"keywords": [
|
|
@@ -57,12 +67,13 @@
|
|
|
57
67
|
},
|
|
58
68
|
"homepage": "https://github.com/spagu/XSLT-Processor#readme",
|
|
59
69
|
"engines": {
|
|
60
|
-
"node": ">=
|
|
70
|
+
"node": ">=20.19.0"
|
|
61
71
|
},
|
|
62
72
|
"devDependencies": {
|
|
63
|
-
"eslint": "^
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"jsdom": "^
|
|
73
|
+
"@eslint/js": "^10.0.1",
|
|
74
|
+
"esbuild": "^0.28.2",
|
|
75
|
+
"eslint": "^10.10.0",
|
|
76
|
+
"jsdom": "^29.1.1",
|
|
77
|
+
"prettier": "^3.9.6"
|
|
67
78
|
}
|
|
68
79
|
}
|
package/src/XSLTProcessor.js
CHANGED
|
@@ -30,6 +30,113 @@ export class XSLTProcessor {
|
|
|
30
30
|
this._engine = null;
|
|
31
31
|
this._stylesheet = null;
|
|
32
32
|
this._parameters = new Map();
|
|
33
|
+
this._stylesheetLoader = null;
|
|
34
|
+
this._documentLoader = null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The underlying XSLT engine (advanced usage).
|
|
39
|
+
*
|
|
40
|
+
* The engine is created lazily by {@link XSLTProcessor#importStylesheet},
|
|
41
|
+
* so this getter returns `null` until a stylesheet has been imported.
|
|
42
|
+
* Prefer the public {@link XSLTProcessor#setStylesheetLoader} over reaching
|
|
43
|
+
* into the engine directly.
|
|
44
|
+
*
|
|
45
|
+
* @returns {import('./xslt/engine.js').XsltEngine|null} The engine, or null before import
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* processor.importStylesheet(xslDoc, '/styles/main.xsl');
|
|
49
|
+
* console.log(processor.engine.outputSettings.method);
|
|
50
|
+
*/
|
|
51
|
+
get engine() {
|
|
52
|
+
return this._engine;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Sets the loader used to resolve `xsl:import` and `xsl:include` references.
|
|
57
|
+
*
|
|
58
|
+
* The loader is synchronous: it MUST return the external stylesheet as a
|
|
59
|
+
* `Document` or as an XML string (which is parsed automatically). Promises
|
|
60
|
+
* are not awaited by the engine, so pre-load remote stylesheets before
|
|
61
|
+
* calling `importStylesheet`.
|
|
62
|
+
*
|
|
63
|
+
* The loader may be set before or after `importStylesheet`. When set before,
|
|
64
|
+
* it is passed to the engine on creation, which is required for the loader to
|
|
65
|
+
* be used while the stylesheet is being compiled. When set after, the live
|
|
66
|
+
* engine is updated as well.
|
|
67
|
+
*
|
|
68
|
+
* @param {((href: string, baseUri?: string) => (Document|string))|null} loader
|
|
69
|
+
* The loader function, or null to remove a previously configured loader
|
|
70
|
+
* @returns {XSLTProcessor} This processor, to allow chaining
|
|
71
|
+
* @throws {TypeError} If the loader is neither a function nor null
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* processor.setStylesheetLoader((href) => readFileSync(href, 'utf8'));
|
|
75
|
+
* processor.importStylesheet(mainStylesheet, '/styles/main.xsl');
|
|
76
|
+
*/
|
|
77
|
+
setStylesheetLoader(loader) {
|
|
78
|
+
if (
|
|
79
|
+
loader !== null &&
|
|
80
|
+
loader !== undefined &&
|
|
81
|
+
typeof loader !== "function"
|
|
82
|
+
) {
|
|
83
|
+
throw new TypeError(
|
|
84
|
+
"Failed to execute 'setStylesheetLoader' on 'XSLTProcessor': The loader argument must be a function or null.",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this._stylesheetLoader = loader ?? null;
|
|
89
|
+
|
|
90
|
+
// Keep an already created engine in sync
|
|
91
|
+
if (this._engine) {
|
|
92
|
+
this._engine.setStylesheetLoader(this._stylesheetLoader);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Sets the loader used to resolve the XSLT `document()` function.
|
|
100
|
+
*
|
|
101
|
+
* The loader is synchronous: it MUST return the referenced document as a
|
|
102
|
+
* `Document`, as an XML string (which is parsed automatically) or as `null`
|
|
103
|
+
* when the document cannot be provided. Returning `null`, like configuring no
|
|
104
|
+
* loader at all, makes `document()` evaluate to an empty node-set rather than
|
|
105
|
+
* failing the transformation.
|
|
106
|
+
*
|
|
107
|
+
* The loader may be set before or after `importStylesheet`; a live engine is
|
|
108
|
+
* kept in sync.
|
|
109
|
+
*
|
|
110
|
+
* @param {((uri: string, baseUri?: string) => (Document|string|null))|null} loader
|
|
111
|
+
* The loader function, or null to remove a previously configured loader
|
|
112
|
+
* @returns {XSLTProcessor} This processor, to allow chaining
|
|
113
|
+
* @throws {TypeError} If the loader is neither a function nor null
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* // Node.js: resolve document() against the file system
|
|
117
|
+
* import { readFileSync } from 'node:fs';
|
|
118
|
+
* processor.setDocumentLoader((uri) => readFileSync(uri, 'utf8'));
|
|
119
|
+
* processor.importStylesheet(xslDoc, '/styles/main.xsl');
|
|
120
|
+
*/
|
|
121
|
+
setDocumentLoader(loader) {
|
|
122
|
+
if (
|
|
123
|
+
loader !== null &&
|
|
124
|
+
loader !== undefined &&
|
|
125
|
+
typeof loader !== "function"
|
|
126
|
+
) {
|
|
127
|
+
throw new TypeError(
|
|
128
|
+
"Failed to execute 'setDocumentLoader' on 'XSLTProcessor': The loader argument must be a function or null.",
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this._documentLoader = loader ?? null;
|
|
133
|
+
|
|
134
|
+
// Keep an already created engine in sync
|
|
135
|
+
if (this._engine) {
|
|
136
|
+
this._engine.setDocumentLoader(this._documentLoader);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return this;
|
|
33
140
|
}
|
|
34
141
|
|
|
35
142
|
/**
|
|
@@ -40,14 +147,17 @@ export class XSLTProcessor {
|
|
|
40
147
|
* <xsl:stylesheet> or <xsl:transform> element.
|
|
41
148
|
*
|
|
42
149
|
* @param {Node} style - The XSLT stylesheet to import (Document or Element)
|
|
150
|
+
* @param {string} [stylesheetUri] - Optional URI of the stylesheet, used as the
|
|
151
|
+
* base URI when resolving relative `xsl:import`/`xsl:include` hrefs. When
|
|
152
|
+
* omitted, hrefs are passed to the loader unresolved.
|
|
43
153
|
* @returns {void}
|
|
44
154
|
*
|
|
45
155
|
* @example
|
|
46
156
|
* const parser = new DOMParser();
|
|
47
157
|
* const xslDoc = parser.parseFromString(xslText, 'application/xml');
|
|
48
|
-
* processor.importStylesheet(xslDoc);
|
|
158
|
+
* processor.importStylesheet(xslDoc, '/styles/main.xsl');
|
|
49
159
|
*/
|
|
50
|
-
importStylesheet(style) {
|
|
160
|
+
importStylesheet(style, stylesheetUri) {
|
|
51
161
|
if (!style) {
|
|
52
162
|
throw new TypeError(
|
|
53
163
|
"Failed to execute 'importStylesheet' on 'XSLTProcessor': 1 argument required, but only 0 present.",
|
|
@@ -70,14 +180,17 @@ export class XSLTProcessor {
|
|
|
70
180
|
}
|
|
71
181
|
|
|
72
182
|
this._stylesheet = style;
|
|
73
|
-
this._engine = new XsltEngine(
|
|
183
|
+
this._engine = new XsltEngine({
|
|
184
|
+
stylesheetLoader: this._stylesheetLoader,
|
|
185
|
+
documentLoader: this._documentLoader,
|
|
186
|
+
});
|
|
74
187
|
|
|
75
188
|
// Apply any previously set parameters
|
|
76
189
|
for (const [key, value] of this._parameters) {
|
|
77
|
-
this._engine.
|
|
190
|
+
this._engine.setParameterValue(key, value);
|
|
78
191
|
}
|
|
79
192
|
|
|
80
|
-
this._engine.importStylesheet(style);
|
|
193
|
+
this._engine.importStylesheet(style, stylesheetUri);
|
|
81
194
|
}
|
|
82
195
|
|
|
83
196
|
/**
|
|
@@ -182,6 +295,55 @@ export class XSLTProcessor {
|
|
|
182
295
|
}
|
|
183
296
|
}
|
|
184
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Transforms the node source by applying the XSLT stylesheet and serializes
|
|
300
|
+
* the result to a string honoring the stylesheet `xsl:output` settings.
|
|
301
|
+
*
|
|
302
|
+
* Non-W3C convenience method: the native XSLTProcessor has no equivalent.
|
|
303
|
+
* Output method, indentation, XML declaration, document type declaration,
|
|
304
|
+
* CDATA sections and `disable-output-escaping` are all honored
|
|
305
|
+
* (XSLT 1.0 section 16).
|
|
306
|
+
*
|
|
307
|
+
* @param {Node} source - The XML document to transform
|
|
308
|
+
* @returns {string|null} The serialized result, or null on a transformation error
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* const xml = processor.transformToString(xmlDoc);
|
|
312
|
+
* // '<?xml version="1.0" encoding="UTF-8"?>\n<BAR>\n <QUX/>\n</BAR>'
|
|
313
|
+
*/
|
|
314
|
+
transformToString(source) {
|
|
315
|
+
if (!source) {
|
|
316
|
+
throw new TypeError(
|
|
317
|
+
"Failed to execute 'transformToString' on 'XSLTProcessor': 1 argument required, but only 0 present.",
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (!this._engine || !this._stylesheet) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
"Failed to execute 'transformToString' on 'XSLTProcessor': No stylesheet has been imported.",
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Validate source node
|
|
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
|
+
}
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
return this._engine.transformToString(source);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
// Match transformToDocument behavior - return null on error
|
|
342
|
+
console.error("XSLT transformation error:", error);
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
185
347
|
/**
|
|
186
348
|
* Sets a parameter in the XSLT stylesheet.
|
|
187
349
|
*
|
|
@@ -212,7 +374,7 @@ export class XSLTProcessor {
|
|
|
212
374
|
|
|
213
375
|
// If engine is already initialized, update it
|
|
214
376
|
if (this._engine) {
|
|
215
|
-
this._engine.
|
|
377
|
+
this._engine.setParameterValue(key, value);
|
|
216
378
|
}
|
|
217
379
|
}
|
|
218
380
|
|
|
@@ -279,7 +441,7 @@ export class XSLTProcessor {
|
|
|
279
441
|
this._parameters.delete(key);
|
|
280
442
|
|
|
281
443
|
if (this._engine) {
|
|
282
|
-
|
|
444
|
+
this._engine.clearParameterValue(key);
|
|
283
445
|
}
|
|
284
446
|
}
|
|
285
447
|
|
|
@@ -297,13 +459,20 @@ export class XSLTProcessor {
|
|
|
297
459
|
this._parameters.clear();
|
|
298
460
|
|
|
299
461
|
if (this._engine) {
|
|
300
|
-
this._engine.
|
|
462
|
+
this._engine.clearParameterValues();
|
|
301
463
|
}
|
|
302
464
|
}
|
|
303
465
|
|
|
304
466
|
/**
|
|
305
467
|
* Removes all parameters and stylesheets from the XSLTProcessor.
|
|
306
468
|
*
|
|
469
|
+
* Per the W3C `XSLTProcessor` semantics, `reset()` clears stylesheet state and
|
|
470
|
+
* parameters only. The stylesheet and document loaders are processor
|
|
471
|
+
* configuration rather than stylesheet state, so they are deliberately
|
|
472
|
+
* preserved and stay effective for the next `importStylesheet()` call. Pass
|
|
473
|
+
* `null` to {@link XSLTProcessor#setStylesheetLoader} or
|
|
474
|
+
* {@link XSLTProcessor#setDocumentLoader} to remove them explicitly.
|
|
475
|
+
*
|
|
307
476
|
* @returns {void}
|
|
308
477
|
*
|
|
309
478
|
* @example
|
package/src/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export {
|
|
|
29
29
|
XSLTProcessor,
|
|
30
30
|
isNativeXSLTSupported,
|
|
31
31
|
installGlobal,
|
|
32
|
-
default
|
|
32
|
+
default,
|
|
33
33
|
} from "./XSLTProcessor.js";
|
|
34
34
|
|
|
35
35
|
// XPath module (for advanced users)
|
|
@@ -46,10 +46,18 @@ export {
|
|
|
46
46
|
// XSLT engine (for advanced users)
|
|
47
47
|
export { XsltEngine, XsltContext } from "./xslt/index.js";
|
|
48
48
|
|
|
49
|
+
// Output serializer (xsl:output, XSLT 1.0 section 16)
|
|
50
|
+
export {
|
|
51
|
+
serializeResult,
|
|
52
|
+
markRawText,
|
|
53
|
+
isRawText,
|
|
54
|
+
resolveOutputSettings,
|
|
55
|
+
} from "./xslt/serializer.js";
|
|
56
|
+
|
|
49
57
|
/**
|
|
50
58
|
* Version information
|
|
51
59
|
*/
|
|
52
|
-
export const VERSION = "1.
|
|
60
|
+
export const VERSION = "1.1.1";
|
|
53
61
|
|
|
54
62
|
/**
|
|
55
63
|
* Check if we're running in a browser environment
|
|
@@ -61,6 +69,4 @@ export const isBrowser =
|
|
|
61
69
|
* Check if we're running in Node.js
|
|
62
70
|
*/
|
|
63
71
|
export const isNode =
|
|
64
|
-
typeof process !== "undefined" &&
|
|
65
|
-
process.versions != null &&
|
|
66
|
-
process.versions.node != null;
|
|
72
|
+
typeof process !== "undefined" && process.versions?.node != null;
|
package/src/xpath/evaluator.js
CHANGED
|
@@ -51,12 +51,29 @@ const FORBIDDEN_VARIABLE_NAMES = Object.freeze([
|
|
|
51
51
|
* XPath evaluation context
|
|
52
52
|
*/
|
|
53
53
|
export class XPathContext {
|
|
54
|
-
|
|
54
|
+
/**
|
|
55
|
+
* @param {Node} node - The context node
|
|
56
|
+
* @param {number} [position] - The context position (1-based)
|
|
57
|
+
* @param {number} [size] - The context size
|
|
58
|
+
* @param {Object} [variables] - Variable bindings by name
|
|
59
|
+
* @param {Object} [namespaces] - Namespace bindings by prefix
|
|
60
|
+
* @param {*} [hostContext] - Opaque context of the host language (XSLT),
|
|
61
|
+
* carried through unchanged so host defined functions can reach it
|
|
62
|
+
*/
|
|
63
|
+
constructor(
|
|
64
|
+
node,
|
|
65
|
+
position = 1,
|
|
66
|
+
size = 1,
|
|
67
|
+
variables = {},
|
|
68
|
+
namespaces = {},
|
|
69
|
+
hostContext = null,
|
|
70
|
+
) {
|
|
55
71
|
this.node = node;
|
|
56
72
|
this.position = position;
|
|
57
73
|
this.size = size;
|
|
58
74
|
this.variables = variables;
|
|
59
75
|
this.namespaces = namespaces;
|
|
76
|
+
this.hostContext = hostContext;
|
|
60
77
|
}
|
|
61
78
|
|
|
62
79
|
clone(overrides = {}) {
|
|
@@ -66,6 +83,7 @@ export class XPathContext {
|
|
|
66
83
|
overrides.size ?? this.size,
|
|
67
84
|
overrides.variables ?? this.variables,
|
|
68
85
|
overrides.namespaces ?? this.namespaces,
|
|
86
|
+
overrides.hostContext ?? this.hostContext,
|
|
69
87
|
);
|
|
70
88
|
}
|
|
71
89
|
}
|
|
@@ -75,7 +93,10 @@ export class XPathContext {
|
|
|
75
93
|
*/
|
|
76
94
|
export class XPathEvaluator {
|
|
77
95
|
constructor(options = {}) {
|
|
78
|
-
this.functions =
|
|
96
|
+
this.functions = Object.assign(
|
|
97
|
+
Object.create(null),
|
|
98
|
+
this.initCoreFunctions(),
|
|
99
|
+
);
|
|
79
100
|
this.maxRecursionDepth =
|
|
80
101
|
options.maxRecursionDepth ?? XPathLimits.MAX_RECURSION_DEPTH;
|
|
81
102
|
this.maxResultSize = options.maxResultSize ?? XPathLimits.MAX_RESULT_SIZE;
|
|
@@ -535,7 +556,8 @@ export class XPathEvaluator {
|
|
|
535
556
|
case "node":
|
|
536
557
|
return true;
|
|
537
558
|
case "text":
|
|
538
|
-
|
|
559
|
+
// The XPath data model has no CDATA sections: they are text nodes
|
|
560
|
+
return node.nodeType === 3 || node.nodeType === 4;
|
|
539
561
|
case "comment":
|
|
540
562
|
return node.nodeType === 8;
|
|
541
563
|
case "processing-instruction":
|
|
@@ -590,15 +612,34 @@ export class XPathEvaluator {
|
|
|
590
612
|
return context.variables[name];
|
|
591
613
|
}
|
|
592
614
|
|
|
615
|
+
/**
|
|
616
|
+
* Register additional functions, for example the XSLT function library.
|
|
617
|
+
*
|
|
618
|
+
* Existing names are overwritten, so a host language can also specialise a
|
|
619
|
+
* core function. Each function is called as `fn(args, context)` with the
|
|
620
|
+
* evaluator as `this`.
|
|
621
|
+
*
|
|
622
|
+
* @param {Object<string, Function>} functions - Functions by name
|
|
623
|
+
* @returns {XPathEvaluator} This evaluator, to allow chaining
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* evaluator.registerFunctions({ 'my:double': (args, ctx) => 2 });
|
|
627
|
+
*/
|
|
628
|
+
registerFunctions(functions) {
|
|
629
|
+
for (const [name, fn] of Object.entries(functions)) {
|
|
630
|
+
this.functions[name] = fn;
|
|
631
|
+
}
|
|
632
|
+
return this;
|
|
633
|
+
}
|
|
634
|
+
|
|
593
635
|
evalFunctionCall(ast, context) {
|
|
594
636
|
const name = ast.prefix ? `${ast.prefix}:${ast.name}` : ast.name;
|
|
595
|
-
const fn = this.functions[name];
|
|
596
637
|
|
|
597
|
-
if (!
|
|
638
|
+
if (!Object.hasOwn(this.functions, name)) {
|
|
598
639
|
throw new Error(`Unknown function: ${name}`);
|
|
599
640
|
}
|
|
600
641
|
|
|
601
|
-
return
|
|
642
|
+
return this.functions[name].call(this, ast.args, context);
|
|
602
643
|
}
|
|
603
644
|
|
|
604
645
|
// Type conversion functions
|
|
@@ -659,7 +700,7 @@ export class XPathEvaluator {
|
|
|
659
700
|
// Document Fragment
|
|
660
701
|
let text = "";
|
|
661
702
|
const walker = (n) => {
|
|
662
|
-
if (n.nodeType === 3) {
|
|
703
|
+
if (n.nodeType === 3 || n.nodeType === 4) {
|
|
663
704
|
text += n.nodeValue || "";
|
|
664
705
|
} else if (n.childNodes) {
|
|
665
706
|
for (const child of n.childNodes) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XSLT element vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the XSLT namespace URI and for the element names
|
|
5
|
+
* the engine can instantiate. `element-available()` reports against this table,
|
|
6
|
+
* so it stays in step with what {@link XsltEngine#processXsltElement} handles.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
/** The XSLT 1.0 namespace URI. */
|
|
12
|
+
export const XSLT_NAMESPACE = "http://www.w3.org/1999/XSL/Transform";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Element names the engine dispatches, i.e. every element that
|
|
16
|
+
* `element-available()` must report as supported.
|
|
17
|
+
*/
|
|
18
|
+
export const XSLT_ELEMENTS = Object.freeze([
|
|
19
|
+
"apply-imports",
|
|
20
|
+
"apply-templates",
|
|
21
|
+
"attribute",
|
|
22
|
+
"call-template",
|
|
23
|
+
"choose",
|
|
24
|
+
"comment",
|
|
25
|
+
"copy",
|
|
26
|
+
"copy-of",
|
|
27
|
+
"element",
|
|
28
|
+
"fallback",
|
|
29
|
+
"for-each",
|
|
30
|
+
"if",
|
|
31
|
+
"message",
|
|
32
|
+
"number",
|
|
33
|
+
"otherwise",
|
|
34
|
+
"param",
|
|
35
|
+
"processing-instruction",
|
|
36
|
+
"sort",
|
|
37
|
+
"text",
|
|
38
|
+
"value-of",
|
|
39
|
+
"variable",
|
|
40
|
+
"when",
|
|
41
|
+
"with-param",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const ELEMENT_SET = new Set(XSLT_ELEMENTS);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check whether an XSLT element local name is supported by the engine.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} localName - Element local name, e.g. `for-each`
|
|
50
|
+
* @returns {boolean} True when the engine instantiates the element
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* isXsltElementAvailable('for-each'); // true
|
|
54
|
+
*/
|
|
55
|
+
export function isXsltElementAvailable(localName) {
|
|
56
|
+
return ELEMENT_SET.has(localName);
|
|
57
|
+
}
|