@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/README.md
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# @tradik/xslt-processor
|
|
2
2
|
|
|
3
|
+
[](https://github.com/spagu/XSLT-Processor)
|
|
4
|
+
[](https://github.com/spagu/XSLT-Processor/stargazers)
|
|
5
|
+
[](https://github.com/spagu/XSLT-Processor/network/members)
|
|
6
|
+
|
|
3
7
|
[](https://github.com/spagu/XSLT-Processor/actions/workflows/test.yml)
|
|
4
8
|
[](https://github.com/spagu/XSLT-Processor/actions/workflows/release.yml)
|
|
5
9
|
[](https://www.npmjs.com/package/@tradik/xslt-processor)
|
|
6
10
|
[](https://opensource.org/licenses/BSD-3-Clause)
|
|
7
|
-
[](https://nodejs.org/)
|
|
8
12
|
[](https://github.com/spagu/XSLT-Processor)
|
|
9
13
|
|
|
14
|
+
> **Source Code:** [github.com/spagu/XSLT-Processor](https://github.com/spagu/XSLT-Processor)
|
|
15
|
+
|
|
10
16
|
JavaScript implementation of XSLTProcessor for browser environments and Node.js CLI. This package provides a complete implementation of the W3C XSLTProcessor API that can be used as a drop-in replacement for the native browser implementation.
|
|
11
17
|
|
|
12
18
|
## Background
|
|
@@ -22,6 +28,7 @@ This library ensures your XSLT-based applications continue to work regardless of
|
|
|
22
28
|
- **1:1 Native API Compatibility**: Drop-in replacement for native `XSLTProcessor`
|
|
23
29
|
- **Full XSLT 1.0 Support**: Implements the complete W3C XSLT 1.0 specification
|
|
24
30
|
- **XPath 1.0 Engine**: Built-in XPath evaluator with all core functions
|
|
31
|
+
- **`xsl:output` Serialization**: `transformToString()` honors method, indent, doctype, CDATA sections and `disable-output-escaping`
|
|
25
32
|
- **Zero Dependencies**: Standalone implementation with no external dependencies
|
|
26
33
|
- **Multiple Formats**: ESM, CommonJS, and browser IIFE bundles
|
|
27
34
|
- **TypeScript Support**: Includes TypeScript declarations
|
|
@@ -136,6 +143,22 @@ xslt data.xml template.xsl -p title="My Page" -p count=10
|
|
|
136
143
|
|
|
137
144
|
# Format output with indentation
|
|
138
145
|
xslt data.xml template.xsl -f -o output.html
|
|
146
|
+
|
|
147
|
+
# Override the output method and drop the XML declaration
|
|
148
|
+
xslt data.xml template.xsl --method text
|
|
149
|
+
xslt data.xml template.xsl --no-declaration
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The output is serialized according to the `xsl:output` element of the
|
|
153
|
+
stylesheet (see [Serializing output](#serializing-output-xsloutput)); the
|
|
154
|
+
options below override individual `xsl:output` settings.
|
|
155
|
+
|
|
156
|
+
All file arguments must live inside the current working directory (symbolic
|
|
157
|
+
links are resolved first). To work with files elsewhere, run the command from
|
|
158
|
+
that directory or point `XSLT_BASE_DIR` at it:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
XSLT_BASE_DIR=/srv/data xslt /srv/data/in.xml /srv/data/t.xsl -o /srv/data/out.html
|
|
139
162
|
```
|
|
140
163
|
|
|
141
164
|
#### CLI Options
|
|
@@ -144,7 +167,10 @@ xslt data.xml template.xsl -f -o output.html
|
|
|
144
167
|
|--------|-------------|
|
|
145
168
|
| `-o, --output <file>` | Write output to file instead of stdout |
|
|
146
169
|
| `-p, --param <n>=<v>` | Set XSLT parameter (can be used multiple times) |
|
|
147
|
-
| `-f, --format` | Format output with indentation |
|
|
170
|
+
| `-f, --format` | Format output with indentation (same as `--indent`) |
|
|
171
|
+
| `--indent` | Override `xsl:output` to `indent="yes"` |
|
|
172
|
+
| `--method <m>` | Override the `xsl:output` method (`xml`, `html`, `xhtml`, `text`) |
|
|
173
|
+
| `--no-declaration` | Override `xsl:output` to omit the XML declaration |
|
|
148
174
|
| `-h, --help` | Show help message |
|
|
149
175
|
| `-v, --version` | Show version number |
|
|
150
176
|
|
|
@@ -162,14 +188,78 @@ const processor = new XSLTProcessor();
|
|
|
162
188
|
|
|
163
189
|
| Method | Description |
|
|
164
190
|
|--------|-------------|
|
|
165
|
-
| `importStylesheet(node)` | Imports an XSLT stylesheet from a Document or Element node |
|
|
191
|
+
| `importStylesheet(node, stylesheetUri?)` | Imports an XSLT stylesheet from a Document or Element node. The optional `stylesheetUri` is the base URI used to resolve relative `xsl:import`/`xsl:include` hrefs |
|
|
166
192
|
| `transformToFragment(source, output)` | Transforms XML and returns a DocumentFragment |
|
|
167
193
|
| `transformToDocument(source)` | Transforms XML and returns an XMLDocument |
|
|
194
|
+
| `transformToString(source)` | Transforms XML and returns the serialized result honoring `xsl:output` (non-W3C extension) |
|
|
168
195
|
| `setParameter(namespaceURI, localName, value)` | Sets an XSLT parameter |
|
|
169
196
|
| `getParameter(namespaceURI, localName)` | Gets an XSLT parameter value |
|
|
170
197
|
| `removeParameter(namespaceURI, localName)` | Removes an XSLT parameter |
|
|
171
198
|
| `clearParameters()` | Removes all parameters |
|
|
172
|
-
| `reset()` | Resets the processor, removing stylesheet and parameters |
|
|
199
|
+
| `reset()` | Resets the processor, removing stylesheet and parameters (the stylesheet and document loaders are kept) |
|
|
200
|
+
| `setStylesheetLoader(loader)` | Sets the loader used to resolve `xsl:import`/`xsl:include`. Returns the processor for chaining |
|
|
201
|
+
| `setDocumentLoader(loader)` | Sets the loader used to resolve the XSLT `document()` function. Returns the processor for chaining |
|
|
202
|
+
|
|
203
|
+
#### Properties
|
|
204
|
+
|
|
205
|
+
| Property | Description |
|
|
206
|
+
|----------|-------------|
|
|
207
|
+
| `engine` | Read-only access to the underlying `XsltEngine` (advanced usage). It is `null` until `importStylesheet()` has been called |
|
|
208
|
+
|
|
209
|
+
### Serializing output (xsl:output)
|
|
210
|
+
|
|
211
|
+
`transformToString(source)` serializes the result tree according to the
|
|
212
|
+
`xsl:output` element of the stylesheet (XSLT 1.0 section 16). Unlike the DOM
|
|
213
|
+
based methods it returns ready to write markup, so the CLI and Node.js users do
|
|
214
|
+
not need a separate `XMLSerializer` or a hand rolled re-indenter.
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
const xslt = parser.parseFromString(`<?xml version="1.0"?>
|
|
218
|
+
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|
219
|
+
<xsl:output method="xml" indent="yes"/>
|
|
220
|
+
<xsl:template match="/"><BAR><QUX/></BAR></xsl:template>
|
|
221
|
+
</xsl:stylesheet>`, 'application/xml');
|
|
222
|
+
|
|
223
|
+
const processor = new XSLTProcessor();
|
|
224
|
+
processor.importStylesheet(xslt);
|
|
225
|
+
|
|
226
|
+
processor.transformToString(xmlDoc);
|
|
227
|
+
// <?xml version="1.0" encoding="UTF-8"?>
|
|
228
|
+
// <BAR>
|
|
229
|
+
// <QUX/>
|
|
230
|
+
// </BAR>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Supported `xsl:output` attributes:
|
|
234
|
+
|
|
235
|
+
| Attribute | Behavior |
|
|
236
|
+
|-----------|----------|
|
|
237
|
+
| `method="xml"` | XML declaration, minimal escaping, empty elements as `<x/>` (default) |
|
|
238
|
+
| `method="html"` | No XML declaration, void elements as `<br>`, minimized boolean attributes, unescaped `script`/`style` |
|
|
239
|
+
| `method="xhtml"` | XML rules with void elements written as `<br />` |
|
|
240
|
+
| `method="text"` | Concatenation of all text nodes, no escaping |
|
|
241
|
+
| `indent="yes"` | Newline plus two-space indentation for element-only content; mixed content and `pre`/`script`/`style`/`textarea` are left untouched |
|
|
242
|
+
| `encoding`, `version`, `standalone` | Written into the XML declaration |
|
|
243
|
+
| `omit-xml-declaration="yes"` | Suppresses the XML declaration |
|
|
244
|
+
| `doctype-public`, `doctype-system` | Emit a `<!DOCTYPE ...>` before the document element |
|
|
245
|
+
| `cdata-section-elements` | Text children of the listed elements are wrapped in `<![CDATA[...]]>`, split around any `]]>` |
|
|
246
|
+
| `media-type` | Parsed and exposed on the settings object |
|
|
247
|
+
|
|
248
|
+
`disable-output-escaping="yes"` on `xsl:text` and `xsl:value-of` is honored: the
|
|
249
|
+
generated text is emitted verbatim, so `<b>` reaches the output as `<b>`.
|
|
250
|
+
|
|
251
|
+
The serializer can also be used on its own, for example on a fragment produced
|
|
252
|
+
by `transformToFragment`:
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
import { serializeResult } from '@tradik/xslt-processor';
|
|
256
|
+
|
|
257
|
+
serializeResult(fragment, { method: 'html', indent: 'yes' });
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
When `method` is absent (or `auto`), the output method is derived from the
|
|
261
|
+
result tree: `html` when the document element is `html` in no namespace, `xml`
|
|
262
|
+
otherwise.
|
|
173
263
|
|
|
174
264
|
### Parameters Example
|
|
175
265
|
|
|
@@ -190,30 +280,135 @@ processor.clearParameters();
|
|
|
190
280
|
|
|
191
281
|
### Using xsl:import and xsl:include
|
|
192
282
|
|
|
193
|
-
To use `xsl:import` and `xsl:include` elements in your stylesheets,
|
|
283
|
+
To use `xsl:import` and `xsl:include` elements in your stylesheets, configure a
|
|
284
|
+
stylesheet loader that tells the processor how to fetch external stylesheets.
|
|
285
|
+
|
|
286
|
+
Call `processor.setStylesheetLoader(...)` **before** `importStylesheet()` -
|
|
287
|
+
references are resolved while the main stylesheet is being compiled, so a loader
|
|
288
|
+
set afterwards is too late for that stylesheet (it still applies to the next
|
|
289
|
+
`importStylesheet()` call).
|
|
290
|
+
|
|
291
|
+
The loader is **synchronous**. It receives the resolved `href` and the `baseUri`
|
|
292
|
+
of the importing stylesheet, and must return either a `Document` or an XML
|
|
293
|
+
`string` (which is parsed automatically). Promises are not awaited, so pre-load
|
|
294
|
+
remote stylesheets before importing.
|
|
194
295
|
|
|
195
296
|
```javascript
|
|
196
297
|
import { XSLTProcessor } from '@tradik/xslt-processor';
|
|
197
298
|
|
|
198
299
|
const processor = new XSLTProcessor();
|
|
199
300
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
301
|
+
// Pre-loaded stylesheets, keyed by resolved URI
|
|
302
|
+
const stylesheets = {
|
|
303
|
+
'/styles/base.xsl': baseXslText,
|
|
304
|
+
'/styles/utils.xsl': utilsXslText,
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
processor.setStylesheetLoader((href, baseUri) => {
|
|
308
|
+
// href: resolved URI of the xsl:import/xsl:include target
|
|
309
|
+
// baseUri: URI of the importing stylesheet (the stylesheetUri you passed in)
|
|
310
|
+
const xml = stylesheets[href];
|
|
311
|
+
if (!xml) {
|
|
312
|
+
throw new Error(`Unknown stylesheet: ${href} (from ${baseUri})`);
|
|
313
|
+
}
|
|
314
|
+
return xml; // a Document is also accepted
|
|
315
|
+
});
|
|
204
316
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
317
|
+
// The second argument is the base URI used to resolve relative hrefs
|
|
318
|
+
processor.importStylesheet(mainStylesheet, '/styles/main.xsl');
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
If you need remote stylesheets in the browser, fetch them first and hand the
|
|
322
|
+
loader a ready-made map:
|
|
323
|
+
|
|
324
|
+
```javascript
|
|
325
|
+
const hrefs = ['/styles/base.xsl', '/styles/utils.xsl'];
|
|
326
|
+
const texts = await Promise.all(
|
|
327
|
+
hrefs.map((href) => fetch(href).then((response) => response.text())),
|
|
328
|
+
);
|
|
329
|
+
const cache = Object.fromEntries(hrefs.map((href, i) => [href, texts[i]]));
|
|
210
330
|
|
|
211
|
-
|
|
212
|
-
|
|
331
|
+
processor.setStylesheetLoader((href) => cache[href]);
|
|
332
|
+
processor.importStylesheet(mainStylesheet, '/styles/main.xsl');
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
#### Node.js example (filesystem loader)
|
|
336
|
+
|
|
337
|
+
```javascript
|
|
338
|
+
import { readFileSync } from 'node:fs';
|
|
339
|
+
import path from 'node:path';
|
|
340
|
+
import { DOMParser } from '@xmldom/xmldom'; // or: new JSDOM(...).window.DOMParser
|
|
341
|
+
import { XSLTProcessor } from '@tradik/xslt-processor';
|
|
342
|
+
|
|
343
|
+
const parser = new DOMParser();
|
|
344
|
+
const mainPath = path.resolve('./styles/main.xsl');
|
|
345
|
+
|
|
346
|
+
const processor = new XSLTProcessor();
|
|
347
|
+
|
|
348
|
+
processor.setStylesheetLoader((href, baseUri) => {
|
|
349
|
+
const filePath = path.resolve(path.dirname(baseUri), href);
|
|
350
|
+
return readFileSync(filePath, 'utf8'); // returned XML string is parsed for you
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const mainStylesheet = parser.parseFromString(
|
|
354
|
+
readFileSync(mainPath, 'utf8'),
|
|
355
|
+
'application/xml',
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
processor.importStylesheet(mainStylesheet, mainPath);
|
|
359
|
+
|
|
360
|
+
const xml = parser.parseFromString(readFileSync('./data.xml', 'utf8'), 'application/xml');
|
|
361
|
+
const result = processor.transformToDocument(xml);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
#### Advanced: the engine accessor
|
|
365
|
+
|
|
366
|
+
After `importStylesheet()`, `processor.engine` exposes the underlying
|
|
367
|
+
`XsltEngine` for advanced inspection (output settings, compiled templates). It
|
|
368
|
+
is `null` before the first import, which is why the loader must be configured
|
|
369
|
+
through `processor.setStylesheetLoader(...)` rather than `processor.engine`.
|
|
370
|
+
|
|
371
|
+
### Using the document() function
|
|
372
|
+
|
|
373
|
+
The XSLT `document()` function loads additional XML documents at transformation
|
|
374
|
+
time. Configure a **synchronous** document loader with
|
|
375
|
+
`processor.setDocumentLoader(...)`; it receives the resolved URI and the base URI
|
|
376
|
+
and returns a `Document`, an XML `string` (parsed automatically) or `null`.
|
|
377
|
+
|
|
378
|
+
Semantics:
|
|
379
|
+
|
|
380
|
+
- `document('')` returns the stylesheet itself, per the XSLT 1.0 specification.
|
|
381
|
+
- A node-set argument loads one document per node and returns their union.
|
|
382
|
+
- Relative URIs are resolved against the `stylesheetUri` passed to
|
|
383
|
+
`importStylesheet()`; fragment identifiers are ignored.
|
|
384
|
+
- Without a loader, or when the loader returns `null`, `document()` evaluates to
|
|
385
|
+
an **empty node-set** instead of failing the transformation.
|
|
386
|
+
- Each resolved URI is loaded once and cached for the life of the processor.
|
|
387
|
+
|
|
388
|
+
```javascript
|
|
389
|
+
import { readFileSync } from 'node:fs';
|
|
390
|
+
import path from 'node:path';
|
|
391
|
+
import { XSLTProcessor } from '@tradik/xslt-processor';
|
|
392
|
+
|
|
393
|
+
const processor = new XSLTProcessor();
|
|
394
|
+
|
|
395
|
+
processor.setDocumentLoader((uri, baseUri) => {
|
|
396
|
+
const filePath = path.resolve(path.dirname(baseUri || '.'), uri);
|
|
397
|
+
try {
|
|
398
|
+
return readFileSync(filePath, 'utf8'); // XML string is parsed for you
|
|
399
|
+
} catch {
|
|
400
|
+
return null; // -> empty node-set, the transformation keeps going
|
|
401
|
+
}
|
|
213
402
|
});
|
|
214
403
|
|
|
215
|
-
// Now xsl:import and xsl:include will work
|
|
216
404
|
processor.importStylesheet(mainStylesheet, '/styles/main.xsl');
|
|
405
|
+
const result = processor.transformToDocument(xmlDoc);
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
```xml
|
|
409
|
+
<xsl:template match="/">
|
|
410
|
+
<rate><xsl:value-of select="document('rates.xml')/rates/eur"/></rate>
|
|
411
|
+
</xsl:template>
|
|
217
412
|
```
|
|
218
413
|
|
|
219
414
|
#### Import vs Include Behavior
|
|
@@ -236,7 +431,12 @@ processor.importStylesheet(mainStylesheet, '/styles/main.xsl');
|
|
|
236
431
|
### Utility Functions
|
|
237
432
|
|
|
238
433
|
```javascript
|
|
239
|
-
import {
|
|
434
|
+
import {
|
|
435
|
+
isNativeXSLTSupported,
|
|
436
|
+
installGlobal,
|
|
437
|
+
serializeResult,
|
|
438
|
+
markRawText
|
|
439
|
+
} from '@tradik/xslt-processor';
|
|
240
440
|
|
|
241
441
|
// Check if native XSLT is functional
|
|
242
442
|
if (!isNativeXSLTSupported()) {
|
|
@@ -246,6 +446,12 @@ if (!isNativeXSLTSupported()) {
|
|
|
246
446
|
// Install as global XSLTProcessor
|
|
247
447
|
installGlobal(); // Only if native not available
|
|
248
448
|
installGlobal(true); // Force install
|
|
449
|
+
|
|
450
|
+
// Serialize any result tree with xsl:output settings
|
|
451
|
+
serializeResult(node, { method: 'xml', indent: 'yes' });
|
|
452
|
+
|
|
453
|
+
// Mark a text node so that it is emitted without escaping
|
|
454
|
+
markRawText(document.createTextNode('<b>raw</b>'));
|
|
249
455
|
```
|
|
250
456
|
|
|
251
457
|
## Complete Example
|
|
@@ -392,6 +598,13 @@ const result = evaluator.evaluate(ast, context);
|
|
|
392
598
|
| `xsl:with-param` | Supported |
|
|
393
599
|
| `xsl:import` | Supported |
|
|
394
600
|
| `xsl:include` | Supported |
|
|
601
|
+
| `xsl:apply-imports` | Supported |
|
|
602
|
+
| `xsl:attribute-set` | Supported (also via `xsl:use-attribute-sets` on literal result elements) |
|
|
603
|
+
| `xsl:key` | Supported (see `key()`) |
|
|
604
|
+
| `xsl:decimal-format` | Supported (see `format-number()`) |
|
|
605
|
+
| `xsl:namespace-alias` | Supported |
|
|
606
|
+
| `xsl:strip-space` / `xsl:preserve-space` | Supported |
|
|
607
|
+
| `xsl:fallback` | Parsed, never instantiated (no extension elements) |
|
|
395
608
|
|
|
396
609
|
## XPath Functions Supported
|
|
397
610
|
|
|
@@ -407,11 +620,28 @@ const result = evaluator.evaluate(ast, context);
|
|
|
407
620
|
### Number Functions
|
|
408
621
|
- `ceiling()`, `floor()`, `number()`, `round()`, `sum()`
|
|
409
622
|
|
|
623
|
+
### XSLT-Defined Functions
|
|
624
|
+
- `current()` - the XSLT current node, also inside predicates
|
|
625
|
+
- `document(object, base?)` - external documents, see `setDocumentLoader()`
|
|
626
|
+
- `element-available(name)`, `function-available(name)` - reflect the real element and function tables
|
|
627
|
+
- `format-number(number, pattern, decimalFormat?)` - full XSLT 1.0 picture strings, honouring `xsl:decimal-format`
|
|
628
|
+
- `generate-id(nodeSet?)` - stable identifier for the life of the transformation
|
|
629
|
+
- `key(name, value)` - `xsl:key` lookup, with lazily built per-document indexes
|
|
630
|
+
- `system-property(name)` - `xsl:version`, `xsl:vendor`, `xsl:vendor-url`
|
|
631
|
+
- `unparsed-entity-uri(name)` - always returns `''` (unparsed entities are not exposed by the DOM)
|
|
632
|
+
|
|
633
|
+
### Conformance Notes
|
|
634
|
+
- CDATA sections count as text everywhere (string-value, `text()`, `xsl:value-of`, `xsl:copy-of`)
|
|
635
|
+
- The identity transform `<xsl:template match="@*|node()"><xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy></xsl:template>` round-trips a document exactly
|
|
636
|
+
- `xsl:number` supports `level="single|multiple|any"` with `count`, `from` and the `1`, `01`, `a`, `A`, `i`, `I` format tokens
|
|
637
|
+
- The result tree is built in a neutral XML document and imported into the output
|
|
638
|
+
document at the end, so element names and namespaces survive an HTML owner document
|
|
639
|
+
|
|
410
640
|
## Development
|
|
411
641
|
|
|
412
642
|
### Prerequisites
|
|
413
643
|
|
|
414
|
-
- Node.js
|
|
644
|
+
- Node.js 22+ (native test runner; CI runs 22, 24 and 26)
|
|
415
645
|
- Docker (optional, for containerized testing)
|
|
416
646
|
|
|
417
647
|
### Setup
|
|
@@ -455,30 +685,38 @@ docker-compose run build
|
|
|
455
685
|
|
|
456
686
|
### Publishing to npm
|
|
457
687
|
|
|
458
|
-
The package is
|
|
688
|
+
The package is published to npm automatically by the `Release` workflow when a
|
|
689
|
+
`v*` tag is pushed (or a GitHub release is published). Publishing uses npm
|
|
690
|
+
**Trusted Publishing** (OIDC): no `NPM_TOKEN` secret and no OTP are involved,
|
|
691
|
+
and every release carries provenance attestations.
|
|
459
692
|
|
|
460
|
-
**
|
|
461
|
-
|
|
462
|
-
|
|
693
|
+
**One-time prerequisite** (npmjs.com -> package `@tradik/xslt-processor` ->
|
|
694
|
+
Settings -> Trusted Publisher): provider *GitHub Actions*, owner `spagu`,
|
|
695
|
+
repository `XSLT-Processor`, workflow `release.yml`, environment left empty.
|
|
696
|
+
Alternatively add an `NPM_TOKEN` repository secret (granular access token
|
|
697
|
+
with publish rights and 2FA bypass); the publish step passes it as
|
|
698
|
+
`NODE_AUTH_TOKEN`. Without either, the `publish` job fails with `E404` and the
|
|
699
|
+
package must be published manually with
|
|
700
|
+
`npm publish --provenance --access public --otp=CODE`.
|
|
463
701
|
|
|
464
|
-
**Release
|
|
702
|
+
**Release process:**
|
|
465
703
|
|
|
466
704
|
```bash
|
|
467
|
-
# 1.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
# 2. Push the tag
|
|
471
|
-
git push origin --tags
|
|
705
|
+
# 1. Bump the version in package.json, package-lock.json, src/index.js and
|
|
706
|
+
# add a CHANGELOG.md entry, then commit to main.
|
|
472
707
|
|
|
473
|
-
#
|
|
708
|
+
# 2. Tag and push the tag; the workflow refuses to publish if the tag does
|
|
709
|
+
# not match package.json.
|
|
710
|
+
git tag v1.1.1
|
|
711
|
+
git push origin v1.1.1
|
|
474
712
|
```
|
|
475
713
|
|
|
476
|
-
**Automated
|
|
477
|
-
1. Runs
|
|
478
|
-
2.
|
|
479
|
-
3.
|
|
480
|
-
4.
|
|
481
|
-
5.
|
|
714
|
+
**Automated workflow:**
|
|
715
|
+
1. Runs lint, formatting check and tests on Node.js 22, 24 and 26
|
|
716
|
+
2. Builds the distribution bundles and verifies the package contents
|
|
717
|
+
3. Checks that the tag matches the `package.json` version
|
|
718
|
+
4. Uploads the `dist/` build artifacts to GitHub
|
|
719
|
+
5. Publishes to npm with provenance
|
|
482
720
|
|
|
483
721
|
## Browser Compatibility
|
|
484
722
|
|
|
@@ -488,13 +726,13 @@ This library provides a JavaScript polyfill for XSLTProcessor that works across
|
|
|
488
726
|
|
|
489
727
|
| Browser | Minimum Version | ES Modules | Status |
|
|
490
728
|
|---------|-----------------|------------|--------|
|
|
491
|
-
| Chrome |
|
|
492
|
-
| Firefox |
|
|
493
|
-
| Safari |
|
|
494
|
-
| Edge |
|
|
495
|
-
| Opera |
|
|
496
|
-
| Samsung Internet |
|
|
497
|
-
| Node.js |
|
|
729
|
+
| Chrome | 92+ | Yes | Fully Supported |
|
|
730
|
+
| Firefox | 92+ | Yes | Fully Supported |
|
|
731
|
+
| Safari | 15.4+ | Yes | Fully Supported |
|
|
732
|
+
| Edge | 92+ | Yes | Fully Supported |
|
|
733
|
+
| Opera | 78+ | Yes | Fully Supported |
|
|
734
|
+
| Samsung Internet | 16+ | Yes | Fully Supported |
|
|
735
|
+
| Node.js | 20.19+ | Yes | Fully Supported (CI: 22, 24, 26) |
|
|
498
736
|
|
|
499
737
|
### Native XSLT Deprecation Timeline
|
|
500
738
|
|
|
@@ -560,7 +798,12 @@ This implementation follows these W3C specifications with comprehensive test cov
|
|
|
560
798
|
| 13 | Messages | Supported | `xsl:message` with `terminate` attribute |
|
|
561
799
|
| 14 | Extensions | Partial | `xsl:fallback` supported |
|
|
562
800
|
| 15 | Fallback | Supported | `xsl:fallback` element |
|
|
563
|
-
| 16 | Output | Supported | `xsl:output`
|
|
801
|
+
| 16 | Output | Supported | `xsl:output` honored by `transformToString()` / `serializeResult()` |
|
|
802
|
+
| 16.1 | XML Output Method | Supported | XML declaration (`encoding`, `version`, `standalone`), `omit-xml-declaration`, `doctype-public`/`doctype-system`, namespace declarations, `indent="yes"` for element-only content |
|
|
803
|
+
| 16.1 | CDATA Sections | Supported | `cdata-section-elements`, split around `]]>` |
|
|
804
|
+
| 16.2 | HTML Output Method | Supported | Void elements as `<br>`, minimized boolean attributes, unescaped `script`/`style`, no re-indent inside `pre`/`script`/`style`/`textarea` |
|
|
805
|
+
| 16.3 | Text Output Method | Supported | Concatenation of all text nodes, no escaping |
|
|
806
|
+
| 16.4 | Disabling Output Escaping | Supported | `disable-output-escaping` on `xsl:text` and `xsl:value-of` |
|
|
564
807
|
|
|
565
808
|
### XPath 1.0 Specification Compliance
|
|
566
809
|
|
|
@@ -625,6 +868,7 @@ This implementation provides full compatibility with the [MDN XSLTProcessor API]
|
|
|
625
868
|
| `importStylesheet(node)` | Supported | Accepts Document or Element |
|
|
626
869
|
| `transformToFragment(source, output)` | Supported | Returns DocumentFragment |
|
|
627
870
|
| `transformToDocument(source)` | Supported | Returns XMLDocument |
|
|
871
|
+
| `transformToString(source)` | Extension | Not part of the W3C API; returns the `xsl:output` serialized result |
|
|
628
872
|
| `setParameter(namespaceURI, localName, value)` | Supported | Full namespace support |
|
|
629
873
|
| `getParameter(namespaceURI, localName)` | Supported | Returns parameter value |
|
|
630
874
|
| `removeParameter(namespaceURI, localName)` | Supported | Removes single parameter |
|
|
@@ -640,8 +884,9 @@ This implementation provides full compatibility with the [MDN XSLTProcessor API]
|
|
|
640
884
|
| XPath 1.0 Axes | 26+ | All 13 axes |
|
|
641
885
|
| DOM Level 3 | 20+ | Core interfaces |
|
|
642
886
|
| XSLTProcessor API | 39+ | All methods |
|
|
887
|
+
| Output Serialization | 79+ | `xsl:output`, CLI, `transformToString()` |
|
|
643
888
|
| Security | 34+ | DoS prevention, prototype pollution |
|
|
644
|
-
| **Total** | **
|
|
889
|
+
| **Total** | **560** | **100% line coverage** |
|
|
645
890
|
|
|
646
891
|
## Style Guide
|
|
647
892
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XSLT Processor CLI - Option Handling
|
|
3
|
+
*
|
|
4
|
+
* Command line option definitions, help/version banners and parameter parsing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Version of the CLI, read from the package manifest.
|
|
15
|
+
* @type {string}
|
|
16
|
+
*/
|
|
17
|
+
export const VERSION = require("../../package.json").version;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* parseArgs option definitions.
|
|
21
|
+
* @type {object}
|
|
22
|
+
*/
|
|
23
|
+
export const CLI_OPTIONS = {
|
|
24
|
+
output: { type: "string", short: "o" },
|
|
25
|
+
param: { type: "string", short: "p", multiple: true },
|
|
26
|
+
format: { type: "boolean", short: "f", default: false },
|
|
27
|
+
indent: { type: "boolean", default: false },
|
|
28
|
+
method: { type: "string" },
|
|
29
|
+
"no-declaration": { type: "boolean", default: false },
|
|
30
|
+
help: { type: "boolean", short: "h", default: false },
|
|
31
|
+
version: { type: "boolean", short: "v", default: false },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Print the usage banner.
|
|
36
|
+
* @returns {void}
|
|
37
|
+
*/
|
|
38
|
+
export function printHelp() {
|
|
39
|
+
console.log(`
|
|
40
|
+
xslt-processor - Transform XML documents using XSLT stylesheets
|
|
41
|
+
|
|
42
|
+
USAGE:
|
|
43
|
+
xslt <xml-file> <xslt-file> [options]
|
|
44
|
+
|
|
45
|
+
ARGUMENTS:
|
|
46
|
+
<xml-file> Path to XML source document
|
|
47
|
+
<xslt-file> Path to XSLT stylesheet
|
|
48
|
+
|
|
49
|
+
OPTIONS:
|
|
50
|
+
-o, --output <file> Write output to file instead of stdout
|
|
51
|
+
-p, --param <n>=<v> Set XSLT parameter (can be used multiple times)
|
|
52
|
+
-f, --format Format output with indentation (same as --indent)
|
|
53
|
+
--indent Override xsl:output to indent="yes"
|
|
54
|
+
--method <m> Override xsl:output method (xml|html|xhtml|text)
|
|
55
|
+
--no-declaration Override xsl:output to omit the XML declaration
|
|
56
|
+
-h, --help Show this help message
|
|
57
|
+
-v, --version Show version number
|
|
58
|
+
|
|
59
|
+
The output is serialized according to the xsl:output element of the
|
|
60
|
+
stylesheet; the options above override individual xsl:output settings.
|
|
61
|
+
|
|
62
|
+
All file arguments must live inside the current working directory, or inside
|
|
63
|
+
the directory named by the XSLT_BASE_DIR environment variable when it is set.
|
|
64
|
+
|
|
65
|
+
EXAMPLES:
|
|
66
|
+
# Basic transformation
|
|
67
|
+
xslt data.xml transform.xsl
|
|
68
|
+
|
|
69
|
+
# Save output to file
|
|
70
|
+
xslt data.xml transform.xsl -o result.html
|
|
71
|
+
|
|
72
|
+
# With parameters
|
|
73
|
+
xslt data.xml transform.xsl -p title="My Page" -p count=10
|
|
74
|
+
|
|
75
|
+
# Multiple parameters with formatted output
|
|
76
|
+
xslt data.xml transform.xsl -p lang=en -p debug=true -f -o output.html
|
|
77
|
+
`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Print the version banner.
|
|
82
|
+
* @returns {void}
|
|
83
|
+
*/
|
|
84
|
+
export function printVersion() {
|
|
85
|
+
console.log(`xslt-processor v${VERSION}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Parse `name=value` parameter arguments.
|
|
90
|
+
*
|
|
91
|
+
* @param {string[]|undefined} params - Raw `--param` values
|
|
92
|
+
* @returns {Record<string, string>} Parsed parameters
|
|
93
|
+
*/
|
|
94
|
+
export function parseParameters(params) {
|
|
95
|
+
const result = {};
|
|
96
|
+
|
|
97
|
+
if (!params || !Array.isArray(params)) {
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (const param of params) {
|
|
102
|
+
const equalIndex = param.indexOf("=");
|
|
103
|
+
if (equalIndex === -1) {
|
|
104
|
+
console.error(
|
|
105
|
+
`Warning: Invalid parameter format "${param}". Expected name=value`,
|
|
106
|
+
);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
result[param.substring(0, equalIndex)] = param.substring(equalIndex + 1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return result;
|
|
114
|
+
}
|