@tradik/xslt-processor 1.0.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +1 -1
- package/README.md +110 -520
- package/bin/lib/decode.js +15 -0
- package/bin/lib/dom.js +177 -0
- package/bin/lib/loaders.js +127 -0
- package/bin/lib/options.js +131 -0
- package/bin/lib/output.js +114 -0
- package/bin/lib/paths.js +186 -0
- package/bin/lib/transform.js +206 -0
- package/bin/xslt.js +73 -168
- package/dist/xslt-processor.browser.js +9564 -1585
- package/dist/xslt-processor.browser.js.map +4 -4
- package/dist/xslt-processor.browser.min.js +13 -2
- package/dist/xslt-processor.browser.min.js.map +4 -4
- package/dist/xslt-processor.cjs +9572 -1586
- package/dist/xslt-processor.cjs.map +4 -4
- package/dist/xslt-processor.d.cts +658 -0
- package/dist/xslt-processor.d.ts +459 -12
- package/dist/xslt-processor.js +9546 -1582
- package/dist/xslt-processor.js.map +4 -4
- package/package.json +71 -20
- package/src/XSLTProcessor.js +494 -48
- package/src/async/abort.js +63 -0
- package/src/async/documentUris.js +128 -0
- package/src/async/loaders.js +134 -0
- package/src/async/preload.js +159 -0
- package/src/async/processor.js +206 -0
- package/src/async/stream.js +125 -0
- package/src/bridge/engine.js +221 -0
- package/src/bridge/loader.js +78 -0
- package/src/bridge/results.js +75 -0
- package/src/bridge/version.js +63 -0
- package/src/index.js +26 -8
- package/src/io/decode.js +140 -0
- package/src/io/readSource.js +167 -0
- package/src/xpath/axes.js +562 -0
- package/src/xpath/documentOrder.js +270 -0
- package/src/xpath/evaluator.js +518 -357
- package/src/xpath/index.js +8 -2
- package/src/xpath/namespaceNodes.js +172 -0
- package/src/xpath/nodeSetFunctions.js +169 -0
- package/src/xpath/parser.js +30 -5
- package/src/xpath/strings.js +183 -0
- package/src/xpath/tokenizer.js +37 -23
- package/src/xslt/attributeSets.js +95 -0
- package/src/xslt/avt.js +103 -0
- package/src/xslt/computedNames.js +91 -0
- package/src/xslt/copying.js +212 -0
- package/src/xslt/declarationNames.js +80 -0
- package/src/xslt/domParsing.js +95 -0
- package/src/xslt/elements.js +57 -0
- package/src/xslt/engine/bindings.js +195 -0
- package/src/xslt/engine/context.js +105 -0
- package/src/xslt/engine/controlFlow.js +145 -0
- package/src/xslt/engine/copyInstructions.js +133 -0
- package/src/xslt/engine/declarations.js +233 -0
- package/src/xslt/engine/functionSupport.js +103 -0
- package/src/xslt/engine/methods.js +33 -0
- package/src/xslt/engine/nodeConstruction.js +187 -0
- package/src/xslt/engine/numbering.js +104 -0
- package/src/xslt/engine/outputDeclaration.js +77 -0
- package/src/xslt/engine/sequenceConstructor.js +228 -0
- package/src/xslt/engine/stylesheetLoading.js +208 -0
- package/src/xslt/engine/templateInvocation.js +253 -0
- package/src/xslt/engine/templateRules.js +243 -0
- package/src/xslt/engine/textInstructions.js +171 -0
- package/src/xslt/engine/topLevel.js +130 -0
- package/src/xslt/engine/transformation.js +263 -0
- package/src/xslt/engine/workStack.js +245 -0
- package/src/xslt/engine.js +184 -1736
- package/src/xslt/exslt/arguments.js +99 -0
- package/src/xslt/exslt/calendar.js +120 -0
- package/src/xslt/exslt/common.js +44 -0
- package/src/xslt/exslt/dateCalc.js +261 -0
- package/src/xslt/exslt/dateFormat.js +150 -0
- package/src/xslt/exslt/dateParse.js +265 -0
- package/src/xslt/exslt/dates.js +259 -0
- package/src/xslt/exslt/duration.js +207 -0
- package/src/xslt/exslt/dynamic.js +59 -0
- package/src/xslt/exslt/index.js +59 -0
- package/src/xslt/exslt/math.js +177 -0
- package/src/xslt/exslt/sets.js +96 -0
- package/src/xslt/exslt/stringOps.js +163 -0
- package/src/xslt/exslt/strings.js +147 -0
- package/src/xslt/exslt/uri.js +92 -0
- package/src/xslt/formatNumber.js +233 -0
- package/src/xslt/forwardsCompatible.js +75 -0
- package/src/xslt/functions.js +270 -0
- package/src/xslt/index.js +38 -1
- package/src/xslt/keys.js +164 -0
- package/src/xslt/literalResult.js +223 -0
- package/src/xslt/matchScope.js +116 -0
- package/src/xslt/number.js +271 -0
- package/src/xslt/numberFormat.js +253 -0
- package/src/xslt/outputNames.js +58 -0
- package/src/xslt/patternCompiler.js +175 -0
- package/src/xslt/patterns.js +324 -0
- package/src/xslt/qname.js +90 -0
- package/src/xslt/resultDocument.js +98 -0
- package/src/xslt/resultNamespaces.js +219 -0
- package/src/xslt/resultTree.js +211 -0
- package/src/xslt/serializer/baseWriter.js +390 -0
- package/src/xslt/serializer/chunks.js +120 -0
- package/src/xslt/serializer/constants.js +92 -0
- package/src/xslt/serializer/encoding.js +327 -0
- package/src/xslt/serializer/escape.js +135 -0
- package/src/xslt/serializer/frames.js +168 -0
- package/src/xslt/serializer/htmlDoctype.js +102 -0
- package/src/xslt/serializer/htmlEntities.js +77 -0
- package/src/xslt/serializer/htmlSerializer.js +239 -0
- package/src/xslt/serializer/indent.js +51 -0
- package/src/xslt/serializer/namespaces.js +68 -0
- package/src/xslt/serializer/rawText.js +41 -0
- package/src/xslt/serializer/settings.js +179 -0
- package/src/xslt/serializer/textSerializer.js +77 -0
- package/src/xslt/serializer/xhtmlDocument.js +103 -0
- package/src/xslt/serializer/xmlSerializer.js +227 -0
- package/src/xslt/serializer.js +90 -0
- package/src/xslt/sort.js +151 -0
- package/src/xslt/spaceNameTests.js +115 -0
- package/src/xslt/stylesheetChecks.js +206 -0
- package/src/xslt/stylesheetNamespaces.js +266 -0
- package/src/xslt/templatePriority.js +45 -0
- package/src/xslt/uri.js +68 -0
- package/src/xslt/variables.js +152 -0
- package/src/xslt/whitespace.js +200 -0
- package/LICENSE +0 -29
- package/src/XSLTProcessor.test.js +0 -930
- package/src/xpath/evaluator.test.js +0 -1852
- package/src/xpath/tokenizer.test.js +0 -224
- package/src/xslt/engine.test.js +0 -3130
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming Output
|
|
3
|
+
*
|
|
4
|
+
* Serializes a transformation result as a WHATWG `ReadableStream<string>`.
|
|
5
|
+
* XSLT 1.0 builds the result tree in memory (templates can read anything
|
|
6
|
+
* they wrote into variables, and the whole source tree), so the tree is built
|
|
7
|
+
* first; what streams is the serialization: chunks are produced on demand
|
|
8
|
+
* (`pull`), one at a time, so the output is never held as one string and a
|
|
9
|
+
* consumer that reads slowly holds serialization back (backpressure).
|
|
10
|
+
*
|
|
11
|
+
* @module async/stream
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { serializeChunks } from "../xslt/serializer.js";
|
|
15
|
+
import { throwIfAborted } from "./abort.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Serialize the transformation of a source node in chunks, as
|
|
19
|
+
* `engine.transformToString` does in one piece.
|
|
20
|
+
*
|
|
21
|
+
* @param {import('../xslt/engine.js').XsltEngine|import('../bridge/engine.js').Xslt3Engine} engine -
|
|
22
|
+
* Engine with an imported stylesheet
|
|
23
|
+
* @param {Node} sourceNode - Source document or element
|
|
24
|
+
* @param {{chunkSize?: number}} [options] - Chunk size in UTF-16 code units
|
|
25
|
+
* @returns {Iterator<string>} The chunks; the result tree is built by this
|
|
26
|
+
* call, serialization happens as the iterator is consumed
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* for (const chunk of transformToChunks(engine, xmlDoc)) out.write(chunk);
|
|
30
|
+
*/
|
|
31
|
+
export function transformToChunks(engine, sourceNode, options = {}) {
|
|
32
|
+
// The @tradik/xslt3 engine (xsltVersion "auto") serializes on its own
|
|
33
|
+
if (engine.transformToChunks) {
|
|
34
|
+
return engine.transformToChunks(sourceNode, options);
|
|
35
|
+
}
|
|
36
|
+
const fragment = engine.buildResultTree(
|
|
37
|
+
sourceNode,
|
|
38
|
+
engine.createDocument(sourceNode),
|
|
39
|
+
);
|
|
40
|
+
return serializeChunks(fragment, engine.outputSettings, options);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Expose lazily produced chunks as a ReadableStream.
|
|
45
|
+
*
|
|
46
|
+
* @param {() => (Iterator<string>|Promise<Iterator<string>>)} start - Called
|
|
47
|
+
* once, on the first pull, to produce the chunk iterator (may be async, e.g.
|
|
48
|
+
* to read the input first)
|
|
49
|
+
* @param {{signal?: AbortSignal}} [options] - Aborting errors the stream with
|
|
50
|
+
* the abort reason and stops the serialization
|
|
51
|
+
* @returns {ReadableStream<string>} The stream; errors of `start` or of the
|
|
52
|
+
* serialization error the stream
|
|
53
|
+
* @throws {TypeError} When the runtime has no ReadableStream
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* const stream = chunkStream(() => serializeChunks(doc, settings));
|
|
57
|
+
*/
|
|
58
|
+
export function chunkStream(start, options = {}) {
|
|
59
|
+
const { signal } = options;
|
|
60
|
+
const Stream = globalThis.ReadableStream;
|
|
61
|
+
if (typeof Stream !== "function") {
|
|
62
|
+
throw new TypeError("ReadableStream is not available in this runtime");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let chunks = null;
|
|
66
|
+
const stop = () => chunks?.return?.();
|
|
67
|
+
let onAbort = null;
|
|
68
|
+
|
|
69
|
+
return new Stream(
|
|
70
|
+
{
|
|
71
|
+
start(controller) {
|
|
72
|
+
if (signal?.aborted) {
|
|
73
|
+
controller.error(signal.reason);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
onAbort = () => {
|
|
77
|
+
stop();
|
|
78
|
+
controller.error(signal.reason);
|
|
79
|
+
};
|
|
80
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
81
|
+
},
|
|
82
|
+
async pull(controller) {
|
|
83
|
+
try {
|
|
84
|
+
chunks ??= await start();
|
|
85
|
+
throwIfAborted(signal);
|
|
86
|
+
const { done, value } = chunks.next();
|
|
87
|
+
if (done) {
|
|
88
|
+
signal?.removeEventListener("abort", onAbort);
|
|
89
|
+
controller.close();
|
|
90
|
+
} else {
|
|
91
|
+
controller.enqueue(value);
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
signal?.removeEventListener("abort", onAbort);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
cancel() {
|
|
99
|
+
signal?.removeEventListener("abort", onAbort);
|
|
100
|
+
stop();
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{ highWaterMark: 1 },
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Transform a source node and stream the serialized result.
|
|
109
|
+
*
|
|
110
|
+
* @param {import('../xslt/engine.js').XsltEngine} engine - Engine with an imported stylesheet
|
|
111
|
+
* @param {Node} sourceNode - Source document or element
|
|
112
|
+
* @param {{signal?: AbortSignal, chunkSize?: number}} [options] - Options
|
|
113
|
+
* @returns {ReadableStream<string>} The serialized result
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* const response = new Response(
|
|
117
|
+
* transformToStream(engine, xmlDoc).pipeThrough(new TextEncoderStream()),
|
|
118
|
+
* );
|
|
119
|
+
*/
|
|
120
|
+
export function transformToStream(engine, sourceNode, options = {}) {
|
|
121
|
+
return chunkStream(
|
|
122
|
+
() => transformToChunks(engine, sourceNode, options),
|
|
123
|
+
options,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Xslt3Engine: @tradik/xslt3 behind the interface of the XSLT 1.0 engine
|
|
3
|
+
*
|
|
4
|
+
* XSLTProcessor drives its engine through a small interface (loaders,
|
|
5
|
+
* parameters, importStylesheet, transformToFragment/Document/String,
|
|
6
|
+
* outputSettings). This class implements it with `compileStylesheet` and
|
|
7
|
+
* `CompiledStylesheet#transform` of @tradik/xslt3, so the W3C API, the
|
|
8
|
+
* asynchronous API and the command line tool run XSLT 2.0/3.0 stylesheets
|
|
9
|
+
* unchanged when `xsltVersion` is "auto".
|
|
10
|
+
*
|
|
11
|
+
* @module bridge/engine
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { parseXml, resolveDomParser } from "../xslt/domParsing.js";
|
|
15
|
+
import { transformationMethods } from "../xslt/engine/transformation.js";
|
|
16
|
+
import { loadedXslt3 } from "./loader.js";
|
|
17
|
+
import { camelCaseOutput, documentResult, fragmentResult } from "./results.js";
|
|
18
|
+
import { stylesheetVersion } from "./version.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A parameter value for @tradik/xslt3: node lists become arrays (sequences
|
|
22
|
+
* of nodes); other values are converted by @tradik/xslt3 (strings to
|
|
23
|
+
* xs:string, numbers to xs:double, booleans, nodes, arrays, Dates, maps).
|
|
24
|
+
*
|
|
25
|
+
* @param {*} value - Value given to setParameter
|
|
26
|
+
* @returns {*} The value to pass
|
|
27
|
+
*/
|
|
28
|
+
function parameterValue(value) {
|
|
29
|
+
const isNodeList =
|
|
30
|
+
typeof value?.length === "number" && typeof value.item === "function";
|
|
31
|
+
return isNodeList ? Array.from(value) : value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Runs XSLT 2.0/3.0 stylesheets with @tradik/xslt3. */
|
|
35
|
+
export class Xslt3Engine {
|
|
36
|
+
/**
|
|
37
|
+
* @param {object} xslt3 - The @tradik/xslt3 module
|
|
38
|
+
* @param {object} [options] - Options of XSLTProcessor
|
|
39
|
+
* @param {Function|null} [options.stylesheetLoader] - xsl:import/include loader
|
|
40
|
+
* @param {Function|null} [options.documentLoader] - doc()/document() loader
|
|
41
|
+
* @param {(() => Date)|null} [options.clock] - current-dateTime() clock
|
|
42
|
+
*/
|
|
43
|
+
constructor(xslt3, options = {}) {
|
|
44
|
+
this.xslt3 = xslt3;
|
|
45
|
+
this.stylesheetLoader = options.stylesheetLoader ?? null;
|
|
46
|
+
this.documentLoader = options.documentLoader ?? null;
|
|
47
|
+
this.clock = options.clock ?? null;
|
|
48
|
+
/** @type {Map<string, *>} Parameters by `{uri}local` or `local` */
|
|
49
|
+
this.parameters = new Map();
|
|
50
|
+
this.compiled = null;
|
|
51
|
+
this.stylesheetDoc = null;
|
|
52
|
+
/** Output settings (camelCase); changes override the stylesheet's. */
|
|
53
|
+
this.outputSettings = {};
|
|
54
|
+
this.declaredOutput = {};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** @param {Function|null} loader - xsl:import/xsl:include loader */
|
|
58
|
+
setStylesheetLoader(loader) {
|
|
59
|
+
this.stylesheetLoader = loader;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** @param {Function|null} loader - doc()/document() loader */
|
|
63
|
+
setDocumentLoader(loader) {
|
|
64
|
+
this.documentLoader = loader;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {string} key - `{uri}local` or `local`
|
|
69
|
+
* @param {*} value - The value
|
|
70
|
+
*/
|
|
71
|
+
setParameterValue(key, value) {
|
|
72
|
+
this.parameters.set(key, parameterValue(value));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** @param {string} key - `{uri}local` or `local` */
|
|
76
|
+
clearParameterValue(key) {
|
|
77
|
+
this.parameters.delete(key);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Remove every parameter. */
|
|
81
|
+
clearParameterValues() {
|
|
82
|
+
this.parameters.clear();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Parse markup returned by a loader.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} text - XML markup
|
|
89
|
+
* @returns {Document} The document
|
|
90
|
+
*/
|
|
91
|
+
parseXmlString(text) {
|
|
92
|
+
return parseXml(text, resolveDomParser(null, this.stylesheetDoc));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A loader result as a document (markup is parsed).
|
|
97
|
+
*
|
|
98
|
+
* @param {Document|string|null} loaded - What a loader returned
|
|
99
|
+
* @returns {Document|null} The document
|
|
100
|
+
*/
|
|
101
|
+
toDocument(loaded) {
|
|
102
|
+
return typeof loaded === "string" ? this.parseXmlString(loaded) : loaded;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Compile a stylesheet.
|
|
107
|
+
*
|
|
108
|
+
* @param {Node} style - Stylesheet document or root element
|
|
109
|
+
* @param {string} [stylesheetUri] - Its URI
|
|
110
|
+
* @returns {void}
|
|
111
|
+
* @throws {Error} The static errors of @tradik/xslt3 (XTSE...)
|
|
112
|
+
*/
|
|
113
|
+
importStylesheet(style, stylesheetUri) {
|
|
114
|
+
this.stylesheetDoc = style.ownerDocument ?? style;
|
|
115
|
+
this.compiled = this.xslt3.compileStylesheet(style, {
|
|
116
|
+
baseUri: stylesheetUri,
|
|
117
|
+
// A module returned as markup is parsed with parseXml
|
|
118
|
+
loadStylesheet: (uri) => this.stylesheetLoader?.(uri),
|
|
119
|
+
parseXml: (text) => this.parseXmlString(text),
|
|
120
|
+
});
|
|
121
|
+
const declared = this.compiled.output;
|
|
122
|
+
this.declaredOutput = camelCaseOutput({ encoding: "UTF-8", ...declared });
|
|
123
|
+
this.outputSettings = { ...this.declaredOutput };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Run the stylesheet on a source.
|
|
128
|
+
*
|
|
129
|
+
* @param {Node} source - Source document or element
|
|
130
|
+
* @returns {{principal: Node, params: object}} The principal result and
|
|
131
|
+
* its serialization parameters
|
|
132
|
+
*/
|
|
133
|
+
run(source) {
|
|
134
|
+
const loader = this.documentLoader;
|
|
135
|
+
const result = this.compiled.transform({
|
|
136
|
+
source,
|
|
137
|
+
params: this.parameters,
|
|
138
|
+
documentLoader: loader && ((uri) => this.toDocument(loader(uri))),
|
|
139
|
+
onMessage: (message) => console.log("XSLT Message:", message.textContent),
|
|
140
|
+
currentDateTime: this.clock?.(),
|
|
141
|
+
});
|
|
142
|
+
return { principal: result.principal, params: this.paramsOf(result) };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The serialization parameters of a result: those @tradik/xslt3 reports,
|
|
147
|
+
* with the output settings changed through `outputSettings` on top.
|
|
148
|
+
*
|
|
149
|
+
* @param {{output: object}} result - A transformation result
|
|
150
|
+
* @returns {Record<string, *>} camelCase parameters
|
|
151
|
+
*/
|
|
152
|
+
paramsOf(result) {
|
|
153
|
+
const params = camelCaseOutput(result.output);
|
|
154
|
+
for (const [name, value] of Object.entries(this.outputSettings)) {
|
|
155
|
+
if (value !== this.declaredOutput[name]) params[name] = value;
|
|
156
|
+
}
|
|
157
|
+
return params;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @param {Node} source - Source document or element
|
|
162
|
+
* @param {Document} output - Document that owns the fragment
|
|
163
|
+
* @returns {DocumentFragment} The result fragment
|
|
164
|
+
*/
|
|
165
|
+
transformToFragment(source, output) {
|
|
166
|
+
const { principal, params } = this.run(source);
|
|
167
|
+
return fragmentResult(principal, params, output, this.xslt3.serialize);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @param {Node} source - Source document or element
|
|
172
|
+
* @returns {Document} The result document
|
|
173
|
+
*/
|
|
174
|
+
transformToDocument(source) {
|
|
175
|
+
const { principal, params } = this.run(source);
|
|
176
|
+
const doc = transformationMethods.createDocument(source);
|
|
177
|
+
return documentResult(principal, params, doc, this.xslt3.serialize);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @param {Node} source - Source document or element
|
|
182
|
+
* @returns {string} The serialized result
|
|
183
|
+
*/
|
|
184
|
+
transformToString(source) {
|
|
185
|
+
const { principal, params } = this.run(source);
|
|
186
|
+
return this.xslt3.serialize(principal, params);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The serialized result in chunks (transformToStream, the CLI).
|
|
191
|
+
*
|
|
192
|
+
* @param {Node} source - Source document or element
|
|
193
|
+
* @param {{chunkSize?: number}} [options] - Chunk size
|
|
194
|
+
* @returns {Iterator<string>} The chunks
|
|
195
|
+
*/
|
|
196
|
+
transformToChunks(source, options = {}) {
|
|
197
|
+
const { principal, params } = this.run(source);
|
|
198
|
+
return this.xslt3.serializeChunks(principal, params, options);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Create the engine of an XSLT 2.0/3.0 stylesheet, with the @tradik/xslt3
|
|
204
|
+
* module loaded before (XSLTProcessor.preload or the asynchronous API).
|
|
205
|
+
*
|
|
206
|
+
* @param {Node} style - The stylesheet (for the error message)
|
|
207
|
+
* @param {object} options - See {@link Xslt3Engine}
|
|
208
|
+
* @returns {Xslt3Engine} The engine
|
|
209
|
+
* @throws {Error} When @tradik/xslt3 has not been loaded
|
|
210
|
+
*/
|
|
211
|
+
export function createXslt3Engine(style, options) {
|
|
212
|
+
const xslt3 = loadedXslt3();
|
|
213
|
+
if (!xslt3) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`This XSLT ${stylesheetVersion(style).toFixed(1)} stylesheet needs @tradik/xslt3: ` +
|
|
216
|
+
'call "await XSLTProcessor.preload()" before importStylesheet(), ' +
|
|
217
|
+
"or use importStylesheetAsync() / transformAsync()",
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return new Xslt3Engine(xslt3, options);
|
|
221
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading @tradik/xslt3
|
|
3
|
+
*
|
|
4
|
+
* @tradik/xslt3 is an optional peer dependency: it is loaded with a dynamic
|
|
5
|
+
* `import()` the first time an XSLT 2.0/3.0 stylesheet needs it (or when
|
|
6
|
+
* `XSLTProcessor.preload()` is called), never statically, so the XSLT 1.0
|
|
7
|
+
* bundles do not contain it. Once loaded it is kept for the synchronous API.
|
|
8
|
+
*
|
|
9
|
+
* @module bridge/loader
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** What to do when @tradik/xslt3 is missing. */
|
|
13
|
+
export const XSLT3_MISSING =
|
|
14
|
+
"install @tradik/xslt3 to run XSLT 2.0/3.0 stylesheets (npm install @tradik/xslt3)";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The default importer. The specifier is a literal so that bundlers of the
|
|
18
|
+
* application can resolve it; the build of this package keeps it external.
|
|
19
|
+
*
|
|
20
|
+
* @returns {Promise<object>} The @tradik/xslt3 module
|
|
21
|
+
*/
|
|
22
|
+
const importXslt3 = () => import("@tradik/xslt3");
|
|
23
|
+
|
|
24
|
+
let importer = importXslt3;
|
|
25
|
+
let loaded = null;
|
|
26
|
+
let pending = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Load @tradik/xslt3 once; concurrent calls share the import.
|
|
30
|
+
*
|
|
31
|
+
* @returns {Promise<object>} The @tradik/xslt3 module
|
|
32
|
+
* @throws {Error} "Cannot load @tradik/xslt3: install ..." when the import
|
|
33
|
+
* fails (the import error is the `cause`); a later call tries again
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const { compileStylesheet } = await loadXslt3();
|
|
37
|
+
*/
|
|
38
|
+
export function loadXslt3() {
|
|
39
|
+
if (loaded) return Promise.resolve(loaded);
|
|
40
|
+
pending ??= Promise.resolve()
|
|
41
|
+
.then(() => importer())
|
|
42
|
+
.then(
|
|
43
|
+
(module) => {
|
|
44
|
+
loaded = module;
|
|
45
|
+
return module;
|
|
46
|
+
},
|
|
47
|
+
(error) => {
|
|
48
|
+
pending = null;
|
|
49
|
+
throw new Error(`Cannot load @tradik/xslt3: ${XSLT3_MISSING}`, {
|
|
50
|
+
cause: error,
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
return pending;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The module loaded by {@link loadXslt3}, for the synchronous API.
|
|
59
|
+
*
|
|
60
|
+
* @returns {object|null} The module, null before it has been loaded
|
|
61
|
+
*/
|
|
62
|
+
export function loadedXslt3() {
|
|
63
|
+
return loaded;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Replace the importer of @tradik/xslt3 and forget the loaded module
|
|
68
|
+
* (tests: simulate a missing package without uninstalling it).
|
|
69
|
+
*
|
|
70
|
+
* @param {(() => Promise<object>)|null} [replacement] - The importer, or
|
|
71
|
+
* null for the default `import("@tradik/xslt3")`
|
|
72
|
+
* @returns {void}
|
|
73
|
+
*/
|
|
74
|
+
export function setXslt3Importer(replacement) {
|
|
75
|
+
importer = replacement ?? importXslt3;
|
|
76
|
+
loaded = null;
|
|
77
|
+
pending = null;
|
|
78
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shaping @tradik/xslt3 Results for the W3C API
|
|
3
|
+
*
|
|
4
|
+
* The principal result of @tradik/xslt3 is a document (a well-formed tree)
|
|
5
|
+
* or a document fragment. `transformToFragment` and `transformToDocument`
|
|
6
|
+
* return it shaped as the XSLT 1.0 engine shapes its own results (as
|
|
7
|
+
* Chrome does): html output parsed by the HTML parser of an HTML owner
|
|
8
|
+
* document, text output wrapped in a `pre` page, xml output as a document
|
|
9
|
+
* with the `doctype-public`/`doctype-system` of xsl:output.
|
|
10
|
+
*
|
|
11
|
+
* @module bridge/results
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
importResultFragment,
|
|
16
|
+
isHtmlDocument,
|
|
17
|
+
parseHtmlFragment,
|
|
18
|
+
wrapTextResult,
|
|
19
|
+
} from "../xslt/resultTree.js";
|
|
20
|
+
import { fillXmlDocument, parseHtmlDocument } from "../xslt/resultDocument.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Serialization parameters by camelCase name (`omit-xml-declaration`
|
|
24
|
+
* becomes `omitXmlDeclaration`), the form the XSLT 1.0 output settings use.
|
|
25
|
+
*
|
|
26
|
+
* @param {Record<string, *>} output - Parameters of @tradik/xslt3
|
|
27
|
+
* @returns {Record<string, *>} The same parameters, camelCase names
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* camelCaseOutput({ "omit-xml-declaration": "yes" }); // { omitXmlDeclaration: "yes" }
|
|
31
|
+
*/
|
|
32
|
+
export function camelCaseOutput(output) {
|
|
33
|
+
return Object.fromEntries(
|
|
34
|
+
Object.entries(output).map(([name, value]) => [
|
|
35
|
+
name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()),
|
|
36
|
+
value,
|
|
37
|
+
]),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The result as a fragment owned by `output`.
|
|
43
|
+
*
|
|
44
|
+
* @param {Node} principal - Principal result (document or fragment)
|
|
45
|
+
* @param {Record<string, *>} params - Its serialization parameters
|
|
46
|
+
* @param {Document} output - The owner document
|
|
47
|
+
* @param {(node: Node, params: object) => string} serialize - Serializer
|
|
48
|
+
* @returns {DocumentFragment} The fragment
|
|
49
|
+
*/
|
|
50
|
+
export function fragmentResult(principal, params, output, serialize) {
|
|
51
|
+
if (isHtmlDocument(output) && params.method === "html") {
|
|
52
|
+
return parseHtmlFragment(serialize(principal, params), output);
|
|
53
|
+
}
|
|
54
|
+
return importResultFragment(principal, output, { htmlElements: true });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The result as a document.
|
|
59
|
+
*
|
|
60
|
+
* @param {Node} principal - Principal result (document or fragment)
|
|
61
|
+
* @param {Record<string, *>} params - Its serialization parameters
|
|
62
|
+
* @param {Document} doc - An empty document to fill
|
|
63
|
+
* @param {(node: Node, params: object) => string} serialize - Serializer
|
|
64
|
+
* @returns {Document} The result document
|
|
65
|
+
*/
|
|
66
|
+
export function documentResult(principal, params, doc, serialize) {
|
|
67
|
+
if (params.method === "text") {
|
|
68
|
+
return wrapTextResult(doc, serialize(principal, params));
|
|
69
|
+
}
|
|
70
|
+
if (params.method === "html") {
|
|
71
|
+
const html = parseHtmlDocument(serialize(principal, params), doc);
|
|
72
|
+
if (html) return html;
|
|
73
|
+
}
|
|
74
|
+
return fillXmlDocument(doc, importResultFragment(principal, doc), params);
|
|
75
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XSLT Version Selection
|
|
3
|
+
*
|
|
4
|
+
* The `xsltVersion` option of XSLTProcessor: "1.0" (the default) runs every
|
|
5
|
+
* stylesheet with the XSLT 1.0 engine, a stylesheet declaring a higher
|
|
6
|
+
* version in forwards-compatible mode (as Chrome does); "auto" hands a
|
|
7
|
+
* stylesheet whose effective version is 2.0 or more to @tradik/xslt3.
|
|
8
|
+
*
|
|
9
|
+
* @module bridge/version
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { declaredVersion } from "../xslt/forwardsCompatible.js";
|
|
13
|
+
|
|
14
|
+
/** Values accepted by the `xsltVersion` option. */
|
|
15
|
+
export const XSLT_VERSION_MODES = Object.freeze(["1.0", "auto"]);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Validate the `xsltVersion` option.
|
|
19
|
+
*
|
|
20
|
+
* @param {string|undefined|null} mode - The option value
|
|
21
|
+
* @returns {"1.0"|"auto"} The mode, "1.0" when not given
|
|
22
|
+
* @throws {RangeError} For any other value
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* xsltVersionMode(undefined); // "1.0"
|
|
26
|
+
*/
|
|
27
|
+
export function xsltVersionMode(mode) {
|
|
28
|
+
const value = mode ?? "1.0";
|
|
29
|
+
if (!XSLT_VERSION_MODES.includes(value)) {
|
|
30
|
+
throw new RangeError(
|
|
31
|
+
`Invalid xsltVersion "${value}": expected "1.0" or "auto"`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The effective version of a stylesheet: the `version` attribute of
|
|
39
|
+
* `xsl:stylesheet`/`xsl:transform`, or the `xsl:version` attribute of a
|
|
40
|
+
* simplified stylesheet (literal result element).
|
|
41
|
+
*
|
|
42
|
+
* @param {Node} style - Stylesheet document or root element
|
|
43
|
+
* @returns {number} The version, NaN when missing or not a number
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* stylesheetVersion(xslDoc); // 2 for <xsl:stylesheet version="2.0">
|
|
47
|
+
*/
|
|
48
|
+
export function stylesheetVersion(style) {
|
|
49
|
+
const root = style.nodeType === 9 ? style.documentElement : style;
|
|
50
|
+
const version = root ? declaredVersion(root) : null;
|
|
51
|
+
return version ? Number(version.trim()) : NaN;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Whether a stylesheet is run by @tradik/xslt3 in the given mode.
|
|
56
|
+
*
|
|
57
|
+
* @param {Node} style - Stylesheet document or root element
|
|
58
|
+
* @param {"1.0"|"auto"} mode - The `xsltVersion` option
|
|
59
|
+
* @returns {boolean} True in "auto" mode for version 2.0 and above
|
|
60
|
+
*/
|
|
61
|
+
export function usesXslt3(style, mode) {
|
|
62
|
+
return mode === "auto" && stylesheetVersion(style) >= 2;
|
|
63
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* xslt-processor
|
|
2
|
+
* @tradik/xslt-processor
|
|
3
3
|
*
|
|
4
4
|
* JavaScript implementation of XSLTProcessor for browser environments.
|
|
5
5
|
*
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* @example
|
|
15
15
|
* // ESM import
|
|
16
|
-
* import { XSLTProcessor, installGlobal } from 'xslt-processor';
|
|
16
|
+
* import { XSLTProcessor, installGlobal } from '@tradik/xslt-processor';
|
|
17
17
|
*
|
|
18
18
|
* // Install as global replacement
|
|
19
19
|
* installGlobal();
|
|
@@ -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)
|
|
@@ -40,16 +40,36 @@ export {
|
|
|
40
40
|
XPathEvaluator,
|
|
41
41
|
XPathContext,
|
|
42
42
|
XPathResultType,
|
|
43
|
+
XPathLimits,
|
|
43
44
|
parse as parseXPath,
|
|
44
45
|
} from "./xpath/index.js";
|
|
45
46
|
|
|
46
47
|
// XSLT engine (for advanced users)
|
|
47
|
-
export {
|
|
48
|
+
export {
|
|
49
|
+
XsltEngine,
|
|
50
|
+
XsltContext,
|
|
51
|
+
XSLT_MAX_RESULT_SIZE,
|
|
52
|
+
XSLT_MAX_EXPRESSION_DEPTH,
|
|
53
|
+
XSLT_MAX_TEMPLATE_DEPTH,
|
|
54
|
+
} from "./xslt/index.js";
|
|
55
|
+
|
|
56
|
+
// Output serializer (xsl:output, XSLT 1.0 section 16)
|
|
57
|
+
export {
|
|
58
|
+
serializeResult,
|
|
59
|
+
serializeChunks,
|
|
60
|
+
DEFAULT_CHUNK_SIZE,
|
|
61
|
+
markRawText,
|
|
62
|
+
isRawText,
|
|
63
|
+
resolveOutputSettings,
|
|
64
|
+
} from "./xslt/serializer.js";
|
|
65
|
+
|
|
66
|
+
// Streaming output on the engine side (advanced users)
|
|
67
|
+
export { transformToChunks, transformToStream } from "./async/stream.js";
|
|
48
68
|
|
|
49
69
|
/**
|
|
50
70
|
* Version information
|
|
51
71
|
*/
|
|
52
|
-
export const VERSION = "1.
|
|
72
|
+
export const VERSION = "1.3.0";
|
|
53
73
|
|
|
54
74
|
/**
|
|
55
75
|
* Check if we're running in a browser environment
|
|
@@ -61,6 +81,4 @@ export const isBrowser =
|
|
|
61
81
|
* Check if we're running in Node.js
|
|
62
82
|
*/
|
|
63
83
|
export const isNode =
|
|
64
|
-
typeof process !== "undefined" &&
|
|
65
|
-
process.versions != null &&
|
|
66
|
-
process.versions.node != null;
|
|
84
|
+
typeof process !== "undefined" && process.versions?.node != null;
|