@tradik/xslt-processor 1.0.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 +29 -0
- package/LICENSE.md +28 -0
- package/README.md +634 -0
- package/bin/xslt.js +208 -0
- package/dist/xslt-processor.browser.js +3302 -0
- package/dist/xslt-processor.browser.js.map +7 -0
- package/dist/xslt-processor.browser.min.js +6 -0
- package/dist/xslt-processor.browser.min.js.map +7 -0
- package/dist/xslt-processor.cjs +3311 -0
- package/dist/xslt-processor.cjs.map +7 -0
- package/dist/xslt-processor.d.ts +211 -0
- package/dist/xslt-processor.js +3271 -0
- package/dist/xslt-processor.js.map +7 -0
- package/package.json +68 -0
- package/src/XSLTProcessor.js +368 -0
- package/src/XSLTProcessor.test.js +930 -0
- package/src/index.js +66 -0
- package/src/xpath/evaluator.js +1012 -0
- package/src/xpath/evaluator.test.js +1852 -0
- package/src/xpath/index.js +67 -0
- package/src/xpath/parser.js +595 -0
- package/src/xpath/tokenizer.js +383 -0
- package/src/xpath/tokenizer.test.js +224 -0
- package/src/xslt/engine.js +1812 -0
- package/src/xslt/engine.test.js +3130 -0
- package/src/xslt/index.js +6 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XSLTProcessor - JavaScript Implementation
|
|
3
|
+
*
|
|
4
|
+
* Native-compatible XSLTProcessor implementation for browser environments.
|
|
5
|
+
* Based on W3C DOM Level 3 XSL Transformations and XSLT 1.0 Specification.
|
|
6
|
+
*
|
|
7
|
+
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/XSLTProcessor
|
|
8
|
+
* XSLT 1.0: http://www.w3.org/TR/1999/REC-xslt-19991116
|
|
9
|
+
* XPath 1.0: http://www.w3.org/TR/1999/REC-xpath-19991116
|
|
10
|
+
*
|
|
11
|
+
* This implementation provides 1:1 API compatibility with the native
|
|
12
|
+
* browser XSLTProcessor. It can be used as a replacement when native
|
|
13
|
+
* XSLT support is deprecated or unavailable.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { XsltEngine } from "./xslt/engine.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* XSLTProcessor
|
|
20
|
+
*
|
|
21
|
+
* Applies XSLT stylesheet transformations to XML documents.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const processor = new XSLTProcessor();
|
|
25
|
+
* processor.importStylesheet(xsltDoc);
|
|
26
|
+
* const fragment = processor.transformToFragment(xmlDoc, document);
|
|
27
|
+
*/
|
|
28
|
+
export class XSLTProcessor {
|
|
29
|
+
constructor() {
|
|
30
|
+
this._engine = null;
|
|
31
|
+
this._stylesheet = null;
|
|
32
|
+
this._parameters = new Map();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Imports the XSLT stylesheet.
|
|
37
|
+
*
|
|
38
|
+
* If the given node is a document node, you can pass in a full XSL Transform
|
|
39
|
+
* or a literal result element transform; otherwise, it must be an
|
|
40
|
+
* <xsl:stylesheet> or <xsl:transform> element.
|
|
41
|
+
*
|
|
42
|
+
* @param {Node} style - The XSLT stylesheet to import (Document or Element)
|
|
43
|
+
* @returns {void}
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* const parser = new DOMParser();
|
|
47
|
+
* const xslDoc = parser.parseFromString(xslText, 'application/xml');
|
|
48
|
+
* processor.importStylesheet(xslDoc);
|
|
49
|
+
*/
|
|
50
|
+
importStylesheet(style) {
|
|
51
|
+
if (!style) {
|
|
52
|
+
throw new TypeError(
|
|
53
|
+
"Failed to execute 'importStylesheet' on 'XSLTProcessor': 1 argument required, but only 0 present.",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Validate node type
|
|
58
|
+
if (style.nodeType !== 1 && style.nodeType !== 9) {
|
|
59
|
+
throw new TypeError(
|
|
60
|
+
"Failed to execute 'importStylesheet' on 'XSLTProcessor': The node provided is not a Document or Element.",
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check for parser errors
|
|
65
|
+
const errorNode = style.querySelector
|
|
66
|
+
? style.querySelector("parsererror")
|
|
67
|
+
: null;
|
|
68
|
+
if (errorNode) {
|
|
69
|
+
throw new Error("XSLT stylesheet contains parse errors");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this._stylesheet = style;
|
|
73
|
+
this._engine = new XsltEngine();
|
|
74
|
+
|
|
75
|
+
// Apply any previously set parameters
|
|
76
|
+
for (const [key, value] of this._parameters) {
|
|
77
|
+
this._engine.globalParameters[key] = { value };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this._engine.importStylesheet(style);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Transforms the node source by applying the XSLT stylesheet.
|
|
85
|
+
* Returns a document fragment.
|
|
86
|
+
*
|
|
87
|
+
* @param {Node} source - The XML document to transform
|
|
88
|
+
* @param {Document} output - The document that will own the generated fragment
|
|
89
|
+
* @returns {DocumentFragment} The transformed result as a DocumentFragment
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* const fragment = processor.transformToFragment(xmlDoc, document);
|
|
93
|
+
* document.getElementById('output').appendChild(fragment);
|
|
94
|
+
*/
|
|
95
|
+
transformToFragment(source, output) {
|
|
96
|
+
if (!source) {
|
|
97
|
+
throw new TypeError(
|
|
98
|
+
"Failed to execute 'transformToFragment' on 'XSLTProcessor': 2 arguments required, but only 0 present.",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!output) {
|
|
103
|
+
throw new TypeError(
|
|
104
|
+
"Failed to execute 'transformToFragment' on 'XSLTProcessor': 2 arguments required, but only 1 present.",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!this._engine || !this._stylesheet) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
"Failed to execute 'transformToFragment' on 'XSLTProcessor': No stylesheet has been imported.",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Validate source node
|
|
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
|
+
}
|
|
124
|
+
|
|
125
|
+
// Validate output document
|
|
126
|
+
if (output.nodeType !== 9) {
|
|
127
|
+
throw new TypeError(
|
|
128
|
+
"Failed to execute 'transformToFragment' on 'XSLTProcessor': The output is not a Document.",
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
return this._engine.transform(source, output);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
// Match native behavior - return null on error
|
|
136
|
+
console.error("XSLT transformation error:", error);
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Transforms the node source by applying the XSLT stylesheet.
|
|
143
|
+
* Returns a full XML document.
|
|
144
|
+
*
|
|
145
|
+
* @param {Node} source - The XML document to transform
|
|
146
|
+
* @returns {XMLDocument} The transformed result as an XMLDocument
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* const resultDoc = processor.transformToDocument(xmlDoc);
|
|
150
|
+
* const serialized = new XMLSerializer().serializeToString(resultDoc);
|
|
151
|
+
*/
|
|
152
|
+
transformToDocument(source) {
|
|
153
|
+
if (!source) {
|
|
154
|
+
throw new TypeError(
|
|
155
|
+
"Failed to execute 'transformToDocument' on 'XSLTProcessor': 1 argument required, but only 0 present.",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!this._engine || !this._stylesheet) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
"Failed to execute 'transformToDocument' on 'XSLTProcessor': No stylesheet has been imported.",
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Validate source node
|
|
166
|
+
if (
|
|
167
|
+
source.nodeType !== 1 &&
|
|
168
|
+
source.nodeType !== 9 &&
|
|
169
|
+
source.nodeType !== 11
|
|
170
|
+
) {
|
|
171
|
+
throw new TypeError(
|
|
172
|
+
"Failed to execute 'transformToDocument' on 'XSLTProcessor': The source is not a valid node type.",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
return this._engine.transformToDocument(source);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
// Match native behavior - return null on error
|
|
180
|
+
console.error("XSLT transformation error:", error);
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Sets a parameter in the XSLT stylesheet.
|
|
187
|
+
*
|
|
188
|
+
* @param {string|null} namespaceURI - The namespace URI of the XSLT parameter (use null for no namespace)
|
|
189
|
+
* @param {string} localName - The local name of the parameter
|
|
190
|
+
* @param {*} value - The value to set (string, number, boolean, or node-set)
|
|
191
|
+
* @returns {void}
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* processor.setParameter(null, 'sortOrder', 'ascending');
|
|
195
|
+
* processor.setParameter('http://example.com/ns', 'limit', 10);
|
|
196
|
+
*/
|
|
197
|
+
setParameter(namespaceURI, localName, value) {
|
|
198
|
+
if (arguments.length < 3) {
|
|
199
|
+
throw new TypeError(
|
|
200
|
+
`Failed to execute 'setParameter' on 'XSLTProcessor': 3 arguments required, but only ${arguments.length} present.`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (typeof localName !== "string" || localName === "") {
|
|
205
|
+
throw new TypeError(
|
|
206
|
+
"Failed to execute 'setParameter' on 'XSLTProcessor': The localName argument must be a non-empty string.",
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const key = namespaceURI ? `{${namespaceURI}}${localName}` : localName;
|
|
211
|
+
this._parameters.set(key, value);
|
|
212
|
+
|
|
213
|
+
// If engine is already initialized, update it
|
|
214
|
+
if (this._engine) {
|
|
215
|
+
this._engine.globalParameters[key] = { value };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Gets the value of a parameter from the XSLT stylesheet.
|
|
221
|
+
*
|
|
222
|
+
* @param {string|null} namespaceURI - The namespace URI of the parameter
|
|
223
|
+
* @param {string} localName - The local name of the parameter
|
|
224
|
+
* @returns {*} The parameter value, or empty string if not set
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* const sortOrder = processor.getParameter(null, 'sortOrder');
|
|
228
|
+
*/
|
|
229
|
+
getParameter(namespaceURI, localName) {
|
|
230
|
+
if (arguments.length < 2) {
|
|
231
|
+
throw new TypeError(
|
|
232
|
+
`Failed to execute 'getParameter' on 'XSLTProcessor': 2 arguments required, but only ${arguments.length} present.`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (typeof localName !== "string") {
|
|
237
|
+
throw new TypeError(
|
|
238
|
+
"Failed to execute 'getParameter' on 'XSLTProcessor': The localName argument must be a string.",
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const key = namespaceURI ? `{${namespaceURI}}${localName}` : localName;
|
|
243
|
+
|
|
244
|
+
if (this._parameters.has(key)) {
|
|
245
|
+
return this._parameters.get(key);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Return empty string for unset parameters (matches native behavior)
|
|
249
|
+
return "";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Removes a parameter from the XSLT processor.
|
|
254
|
+
*
|
|
255
|
+
* The XSLTProcessor will use the default value for the parameter
|
|
256
|
+
* as specified in the XSLT stylesheet.
|
|
257
|
+
*
|
|
258
|
+
* @param {string|null} namespaceURI - The namespace URI of the parameter
|
|
259
|
+
* @param {string} localName - The local name of the parameter
|
|
260
|
+
* @returns {void}
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* processor.removeParameter(null, 'sortOrder');
|
|
264
|
+
*/
|
|
265
|
+
removeParameter(namespaceURI, localName) {
|
|
266
|
+
if (arguments.length < 2) {
|
|
267
|
+
throw new TypeError(
|
|
268
|
+
`Failed to execute 'removeParameter' on 'XSLTProcessor': 2 arguments required, but only ${arguments.length} present.`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (typeof localName !== "string") {
|
|
273
|
+
throw new TypeError(
|
|
274
|
+
"Failed to execute 'removeParameter' on 'XSLTProcessor': The localName argument must be a string.",
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const key = namespaceURI ? `{${namespaceURI}}${localName}` : localName;
|
|
279
|
+
this._parameters.delete(key);
|
|
280
|
+
|
|
281
|
+
if (this._engine) {
|
|
282
|
+
delete this._engine.globalParameters[key];
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Removes all set parameters from the XSLTProcessor.
|
|
288
|
+
*
|
|
289
|
+
* The processor will use default values specified in the XSLT stylesheet.
|
|
290
|
+
*
|
|
291
|
+
* @returns {void}
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* processor.clearParameters();
|
|
295
|
+
*/
|
|
296
|
+
clearParameters() {
|
|
297
|
+
this._parameters.clear();
|
|
298
|
+
|
|
299
|
+
if (this._engine) {
|
|
300
|
+
this._engine.globalParameters = {};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Removes all parameters and stylesheets from the XSLTProcessor.
|
|
306
|
+
*
|
|
307
|
+
* @returns {void}
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* processor.reset();
|
|
311
|
+
* // Now need to call importStylesheet() again before transforming
|
|
312
|
+
*/
|
|
313
|
+
reset() {
|
|
314
|
+
this._engine = null;
|
|
315
|
+
this._stylesheet = null;
|
|
316
|
+
this._parameters.clear();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Check if native XSLTProcessor is available and functional
|
|
322
|
+
*
|
|
323
|
+
* @returns {boolean} True if native XSLTProcessor works correctly
|
|
324
|
+
*/
|
|
325
|
+
export function isNativeXSLTSupported() {
|
|
326
|
+
if (typeof globalThis.XSLTProcessor === "undefined") {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
const processor = new globalThis.XSLTProcessor();
|
|
332
|
+
const parser = new DOMParser();
|
|
333
|
+
|
|
334
|
+
const xslt = parser.parseFromString(
|
|
335
|
+
`<?xml version="1.0"?>
|
|
336
|
+
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|
337
|
+
<xsl:template match="/"><test/></xsl:template>
|
|
338
|
+
</xsl:stylesheet>`,
|
|
339
|
+
"application/xml",
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
processor.importStylesheet(xslt);
|
|
343
|
+
|
|
344
|
+
const xml = parser.parseFromString("<root/>", "application/xml");
|
|
345
|
+
const result = processor.transformToFragment(xml, document);
|
|
346
|
+
|
|
347
|
+
return result !== null && result.childNodes.length > 0;
|
|
348
|
+
} catch {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Install as global XSLTProcessor replacement if native is not functional
|
|
355
|
+
*
|
|
356
|
+
* @param {boolean} force - Force installation even if native is available
|
|
357
|
+
* @returns {boolean} True if installed as global
|
|
358
|
+
*/
|
|
359
|
+
export function installGlobal(force = false) {
|
|
360
|
+
if (!force && isNativeXSLTSupported()) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
globalThis.XSLTProcessor = XSLTProcessor;
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export default XSLTProcessor;
|