This is a polyfill for the XSLT W3C 1.0
standard, roughly as shipped in the Chrome browser. It is intended as a
replacement for using the XSLT processing machinery built into browsers. It
does still rely on the XML parsing machinery (e.g. parseFromString(xmlText, "application/xml")) for some things.
Note that the XSLT 1.0 standard is very old, and XSLT has evolved well beyond the version shipped in web browsers. There are also more up-to-date JS-based implementations of XSLT that support the most current standards. One such example is SaxonJS. You should check that out if you're looking for "real" XSLT support; this polyfill is intended merely as a stopgap that can be used to maintain functionality.
If you have an XML document that contains an XSL processing instruction, like this:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="demo.xsl"?>
<page>
<message>
Hello World.
</message>
</page>
You can convert it to use this polyfill by simply adding the polyfill directly
to the XML, like this (note the new <script> element):
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="demo.xsl"?>
<page>
<script src="../xslt-polyfill.min.js" xmlns="http://www.w3.org/1999/xhtml"></script>
<message>
Hello World.
</message>
</page>
This will automatically load the XML and XSLT, process it with this
XSLT polyfill, and replace the page with the result of the transformation.
The example above is available in the test/ folder of this repo:
demo.xml.
The polyfill also provides a full implementation of the XSLTProcessor class,
so that code like this will also work:
<!DOCTYPE html>
<script src="xslt-polyfill.min.js" charset="utf-8"></script>
<script>
const xsltProcessor = new XSLTProcessor();
xsltProcessor.importStylesheet(xsltDoc);
xsltProcessor.transformToFragment(xmlDoc, document);
</script>
The example above is available in the test/ folder of this repo:
XSLTProcessor_example.html.
By default, the polyfill installs itself unconditionally, replacing
window.XSLTProcessor even in browsers that have working native XSLT support.
To only install the polyfill when it is actually needed (i.e. when
window.XSLTProcessor is missing, isn't native code, or can't be
constructed), set window.xsltUsePolyfillOnlyIfNeeded to true before the
polyfill script runs:
<!DOCTYPE html>
<script>window.xsltUsePolyfillOnlyIfNeeded = true;</script>
<script src="xslt-polyfill.min.js" charset="utf-8"></script>
The same works in an XML document, as long as the <script> elements are in
the XHTML namespace:
<script xmlns="http://www.w3.org/1999/xhtml">window.xsltUsePolyfillOnlyIfNeeded = true;</script>
<script src="../xslt-polyfill.min.js" xmlns="http://www.w3.org/1999/xhtml"></script>
Notes:
window.XSLTProcessor with its own
implementation and exposes its helper functions
(xsltPolyfillReady(), loadXmlWithXsltFromUrl(),
parseAndReplaceCurrentXMLDoc(), etc.).<?xml-stylesheet?> processing instruction: the polyfill
performs that transformation even in a browser with native XSLT support.
(This only matters if the browser hasn't already transformed the document
natively.) Use window.xsltDontAutoloadXmlDocs = true to suppress the
automatic transformation.When the polyfill is used to automatically transform a page (e.g. via an
injected <script> tag), it can take a moment to fetch the necessary files and
compile the Wasm engine. To improve user experience, the polyfill can display a
loading spinner while processing.
To enable the loading spinner, you can configure the
window.xsltPolyfillSpinner property before the polyfill script initializes.
Set this property to the string you want to display to the user. When the
transformation completes, the polyfill or your scripts can clear the spinner by
setting it to null.
<script xmlns="http://www.w3.org/1999/xhtml">
// Enable the spinner by setting the text to display
window.xsltPolyfillSpinner = 'Processing XSLT, please wait...';
</script>
<script src="../xslt-polyfill.min.js" xmlns="http://www.w3.org/1999/xhtml"></script>
You can also dynamically set or clear the spinner at runtime by modifying the property:
// Show the spinner with new text
window.xsltPolyfillSpinner = 'Updating content...';
// Hide the spinner and show the document body
window.xsltPolyfillSpinner = null;
The polyfill is powered by a WebAssembly port of the libxslt and libxml2 C libraries, providing a standards-compliant XSLT 1.0 engine.
XSLTProcessor API, including
importStylesheet, transformToDocument, transformToFragment, and
parameter management.<script> tag in an XML
document (and provided the <script> has
xmlns="http://www.w3.org/1999/xhtml"), the polyfill automatically detects
<?xml-stylesheet?> processing instructions. It fetches the linked XSLT,
re-fetches the source document, performs the transformation, and replaces the
document's content with the result.<xsl:import>, <xsl:include>, or document(), are
fetched using the fetch() API and are thus subject to CORS. The source
document is re-fetched to ensure the WebAssembly engine receives the clean,
original byte stream, avoiding any modifications or tag stripping introduced
by the browser's initial XML parsing.<script> elements and fires
synthetic DOMContentLoaded and load events.type="text/xsl" or
type="application/xslt+xml". Because the underlying document remains an
XML/XHTML document, the polyfill monkey-patches document.createElement to
ensure new elements are created in the XHTML namespace.Note that as of now, there are a few things that don't work perfectly:
chrome://flags/#xslt. The XSLTProcessor API
always uses the polyfill by default, see
Skipping the Polyfill.parseAndReplaceCurrentXMLDoc() function will replace the contents of
the current document (an XHTML document) with the transformed content.
Because XHTML always renders in no-quirks mode, if the transformed (HTML)
output content doesn't include a <!DOCTYPE>, then it ordinarily would
have rendered in quirks mode, which is different.<script>
elements so that they execute. This means the behavior will be slightly
different from native: all (legacy synchronous) scripts will see the full
DOM, not just the DOM "above" the script. Also, a synthetic load event
will be fired after all scripts are inserted, since no trusted load will be
fired either.<script>'s, and those
scripts likely expect the document to be an HTML document, and not the XML
document mentioned above, some things need to be monkeypatched by this
polyfill. For example, document.createElement() is patched so that it
creates elements in the XHTML namespace.<xsl:include> or <xsl:import> in the XML source, these
requests are subject to CORS, which might block the request. The browser-
native XSLT processor is able to load these resources, despite the CORS
violation, leading to a behavior difference.XSLTProcessorAsync which has async versions of its methods.There are a few tests and demos in the test/ directory:
test_suite.html: a test suite that runs through various cases, with and without
the polyfill. To run this suite, you must generate the local test files, via
node test/testcase_generator.js.XSLTProcessor_example.html: a test of the XSLTProcessor polyfill, which
offers JS-based XSLT processing.
[Run]demo.xml: a simple XML file that has the polyfill loaded by a single added
<script> tag. The polyfill loads and automatically does the XSLT transformation,
replacing the document.
[Run]demo_large.xml: a much larger example, taken from a public site, which
does the same as demo.xml, but with a more complex/realistic document.
[Run]Make sure you checkout the repo including it's git submodules:
$ git clone --recursive https://github.com/mfreed7/xslt_polyfill.git
The build assumes several tools such as emscripten and make.
The package mangler/compressor terser is managed locally via npm:
npm install
Given the dependencies listed above, you must first install the npm dependencies:
npm install
Then, the polyfill can be built with one command:
npm run build
This will produce xslt-polyfill.min.js, which is the minified polyfill
suitable for production use.
If you find issues with the polyfill, feel free to file them here. Even better, if you would like to contribute to this polyfill, I'm happy to review pull requests. Thanks in advance!
This polyfill is also published on npm:
It is also incorporated into a Chrome extension, which automatically applies the polyfill to raw XML files that contain XSLT stylesheets:
JavaScript
60.8%
XSLT
20.3%
C
11.7%
HTML
4.6%
Makefile
1.7%
CSS
1.0%
This is a polyfill for the XSLT W3C 1.0
standard, roughly as shipped in the Chrome browser. It is intended as a
replacement for using the XSLT processing machinery built into browsers. It
does still rely on the XML parsing machinery (e.g. parseFromString(xmlText, "application/xml")) for some things.
Note that the XSLT 1.0 standard is very old, and XSLT has evolved well beyond the version shipped in web browsers. There are also more up-to-date JS-based implementations of XSLT that support the most current standards. One such example is SaxonJS. You should check that out if you're looking for "real" XSLT support; this polyfill is intended merely as a stopgap that can be used to maintain functionality.
If you have an XML document that contains an XSL processing instruction, like this:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="demo.xsl"?>
<page>
<message>
Hello World.
</message>
</page>
You can convert it to use this polyfill by simply adding the polyfill directly
to the XML, like this (note the new <script> element):
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="demo.xsl"?>
<page>
<script src="../xslt-polyfill.min.js" xmlns="http://www.w3.org/1999/xhtml"></script>
<message>
Hello World.
</message>
</page>
This will automatically load the XML and XSLT, process it with this
XSLT polyfill, and replace the page with the result of the transformation.
The example above is available in the test/ folder of this repo:
demo.xml.
The polyfill also provides a full implementation of the XSLTProcessor class,
so that code like this will also work:
<!DOCTYPE html>
<script src="xslt-polyfill.min.js" charset="utf-8"></script>
<script>
const xsltProcessor = new XSLTProcessor();
xsltProcessor.importStylesheet(xsltDoc);
xsltProcessor.transformToFragment(xmlDoc, document);
</script>
The example above is available in the test/ folder of this repo:
XSLTProcessor_example.html.
By default, the polyfill installs itself unconditionally, replacing
window.XSLTProcessor even in browsers that have working native XSLT support.
To only install the polyfill when it is actually needed (i.e. when
window.XSLTProcessor is missing, isn't native code, or can't be
constructed), set window.xsltUsePolyfillOnlyIfNeeded to true before the
polyfill script runs:
<!DOCTYPE html>
<script>window.xsltUsePolyfillOnlyIfNeeded = true;</script>
<script src="xslt-polyfill.min.js" charset="utf-8"></script>
The same works in an XML document, as long as the <script> elements are in
the XHTML namespace:
<script xmlns="http://www.w3.org/1999/xhtml">window.xsltUsePolyfillOnlyIfNeeded = true;</script>
<script src="../xslt-polyfill.min.js" xmlns="http://www.w3.org/1999/xhtml"></script>
Notes:
window.XSLTProcessor with its own
implementation and exposes its helper functions
(xsltPolyfillReady(), loadXmlWithXsltFromUrl(),
parseAndReplaceCurrentXMLDoc(), etc.).<?xml-stylesheet?> processing instruction: the polyfill
performs that transformation even in a browser with native XSLT support.
(This only matters if the browser hasn't already transformed the document
natively.) Use window.xsltDontAutoloadXmlDocs = true to suppress the
automatic transformation.When the polyfill is used to automatically transform a page (e.g. via an
injected <script> tag), it can take a moment to fetch the necessary files and
compile the Wasm engine. To improve user experience, the polyfill can display a
loading spinner while processing.
To enable the loading spinner, you can configure the
window.xsltPolyfillSpinner property before the polyfill script initializes.
Set this property to the string you want to display to the user. When the
transformation completes, the polyfill or your scripts can clear the spinner by
setting it to null.
<script xmlns="http://www.w3.org/1999/xhtml">
// Enable the spinner by setting the text to display
window.xsltPolyfillSpinner = 'Processing XSLT, please wait...';
</script>
<script src="../xslt-polyfill.min.js" xmlns="http://www.w3.org/1999/xhtml"></script>
You can also dynamically set or clear the spinner at runtime by modifying the property:
// Show the spinner with new text
window.xsltPolyfillSpinner = 'Updating content...';
// Hide the spinner and show the document body
window.xsltPolyfillSpinner = null;
The polyfill is powered by a WebAssembly port of the libxslt and libxml2 C libraries, providing a standards-compliant XSLT 1.0 engine.
XSLTProcessor API, including
importStylesheet, transformToDocument, transformToFragment, and
parameter management.<script> tag in an XML
document (and provided the <script> has
xmlns="http://www.w3.org/1999/xhtml"), the polyfill automatically detects
<?xml-stylesheet?> processing instructions. It fetches the linked XSLT,
re-fetches the source document, performs the transformation, and replaces the
document's content with the result.<xsl:import>, <xsl:include>, or document(), are
fetched using the fetch() API and are thus subject to CORS. The source
document is re-fetched to ensure the WebAssembly engine receives the clean,
original byte stream, avoiding any modifications or tag stripping introduced
by the browser's initial XML parsing.<script> elements and fires
synthetic DOMContentLoaded and load events.type="text/xsl" or
type="application/xslt+xml". Because the underlying document remains an
XML/XHTML document, the polyfill monkey-patches document.createElement to
ensure new elements are created in the XHTML namespace.Note that as of now, there are a few things that don't work perfectly:
chrome://flags/#xslt. The XSLTProcessor API
always uses the polyfill by default, see
Skipping the Polyfill.parseAndReplaceCurrentXMLDoc() function will replace the contents of
the current document (an XHTML document) with the transformed content.
Because XHTML always renders in no-quirks mode, if the transformed (HTML)
output content doesn't include a <!DOCTYPE>, then it ordinarily would
have rendered in quirks mode, which is different.<script>
elements so that they execute. This means the behavior will be slightly
different from native: all (legacy synchronous) scripts will see the full
DOM, not just the DOM "above" the script. Also, a synthetic load event
will be fired after all scripts are inserted, since no trusted load will be
fired either.<script>'s, and those
scripts likely expect the document to be an HTML document, and not the XML
document mentioned above, some things need to be monkeypatched by this
polyfill. For example, document.createElement() is patched so that it
creates elements in the XHTML namespace.<xsl:include> or <xsl:import> in the XML source, these
requests are subject to CORS, which might block the request. The browser-
native XSLT processor is able to load these resources, despite the CORS
violation, leading to a behavior difference.XSLTProcessorAsync which has async versions of its methods.There are a few tests and demos in the test/ directory:
test_suite.html: a test suite that runs through various cases, with and without
the polyfill. To run this suite, you must generate the local test files, via
node test/testcase_generator.js.XSLTProcessor_example.html: a test of the XSLTProcessor polyfill, which
offers JS-based XSLT processing.
[Run]demo.xml: a simple XML file that has the polyfill loaded by a single added
<script> tag. The polyfill loads and automatically does the XSLT transformation,
replacing the document.
[Run]demo_large.xml: a much larger example, taken from a public site, which
does the same as demo.xml, but with a more complex/realistic document.
[Run]Make sure you checkout the repo including it's git submodules:
$ git clone --recursive https://github.com/mfreed7/xslt_polyfill.git
The build assumes several tools such as emscripten and make.
The package mangler/compressor terser is managed locally via npm:
npm install
Given the dependencies listed above, you must first install the npm dependencies:
npm install
Then, the polyfill can be built with one command:
npm run build
This will produce xslt-polyfill.min.js, which is the minified polyfill
suitable for production use.
If you find issues with the polyfill, feel free to file them here. Even better, if you would like to contribute to this polyfill, I'm happy to review pull requests. Thanks in advance!
This polyfill is also published on npm:
It is also incorporated into a Chrome extension, which automatically applies the polyfill to raw XML files that contain XSLT stylesheets:
JavaScript
60.8%
XSLT
20.3%
C
11.7%
HTML
4.6%
Makefile
1.7%
CSS
1.0%