XSLT tester and transformer
Run an XSL stylesheet against an XML document in this tab. XSLT 1.0, because that is what a browser has.
- SECURE
- NO UPLOADS
- NO SIGNUP
- BROWSER BASED
- FREE
- FOREVER.
Support us with a link or a share
The result appears here as you type.
XSLT 1.0, and the reason it is not a limitation you can shop around
Every browser ships an XSLT engine, and in Chrome and Safari it is the same one:
libxslt. Ask it what it is and both answer identically —
system-property('xsl:vendor') returns libxslt and
system-property('xsl:version') returns 1.0. That is the whole
story of which version runs here. There is no setting, no 2.0 mode and no upgrade path,
because the engine is part of the browser rather than something this page downloads.
A stylesheet that declares version="2.0" is not rejected for the number alone;
XSLT says a processor should tolerate a higher version declaration and fail only on the
constructs it cannot run. So the number is ignored here too, and instead the page reads your
stylesheet for the things that will actually stop it: xsl: elements that do not
exist in 1.0 — xsl:for-each-group, xsl:analyze-string,
xsl:function — and XPath 2.0 function names such as upper-case(,
tokenize(, replace( or matches(. Those names are
reported as what they are: text found in your stylesheet, not a diagnosis of your bug. If you
need real 2.0 or 3.0, you need Saxon or another processor outside a browser, and no web page
can pretend otherwise without shipping a megabyte of engine to do it.
What an XSLT tester can honestly tell you when the stylesheet fails
Two kinds of failure exist here, and they are told apart because they are genuinely different.
If either document is not well-formed XML, the browser's parser reports an exact line and an exact column, and you get both, with the offending line printed and a caret under the character. That is a real position from a real parser, so it is worth showing.
If the stylesheet parses but the engine refuses to run it, there is no position to show —
and this is the part most testers handle badly. transformToDocument() returns
null. It does not throw, it sets no error code, it names no line, and nothing
arrives on window.onerror. Four different faults were measured against both
engines for this page — a malformed XPath expression, a document whose root is not
xsl:stylesheet, a call to upper-case(), and an
xsl:message terminate="yes" — and all four produced exactly the same silence in
both. So the page says there is no position rather than inventing one, and then lists only
what it could check for itself: whether the root really is a stylesheet, what version it
claims, which xsl: elements fall outside 1.0, and which later function names
appear in the text. When none of those is wrong, it says that too, and names the usual
remaining suspects instead of guessing between them.
A third case is not a failure at all. A transform that runs and produces nothing has matched
a template and built an empty result, which almost always means a select or
match pattern naming an element that is not in your source. Case and namespace
prefix are both part of an XML name, so Book and book are different
elements and so are item and a:item. The page labels that outcome
as a result and says so.
Your namespace declarations do not have to be on the root element
Some server-side XSLT testers require every namespace declaration to sit on the root element
and will refuse a document that puts one lower down. That rule was measured here rather than
copied, because a rule inherited without measurement is just someone else's plumbing turned
into your problem. Five placements were run through both browsers: the prefix declared on the
stylesheet root, on a child element of the source, on the xsl:template that uses
it, on the xsl:value-of itself, and as a default namespace on a child of the
source. Every one of them selected the right node, and the output was byte-identical in Chrome
and Safari.
Where the declaration sits changes only which prefixes get copied onto a literal result element, which is what XSLT 1.0 specifies that it does. So there is no root-element rule on this page, and a stylesheet that another tester rejected for this reason will very likely run here unchanged.
The one place Chrome and Safari disagree, and where it is named
Twenty-four transforms were run through both browsers and compared byte for byte: namespaces,
entities, CDATA sections, cdata-section-elements,
disable-output-escaping, comments, processing instructions, empty elements,
attribute order, format-number, xsl:sort with a language,
indent="yes", the identity transform, text output, a derived output method,
doctype-public, a transform matching nothing, and astral characters.
Twenty-three came out identical.
One did not. With method="html" and a <head> in the result,
the browser's own document inserts a character-encoding declaration as the first child of
that head — Chrome writes <meta charset="UTF-8"> and Safari writes
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">. It is
not in your stylesheet and it is not the same length, so the same run produces 91 bytes in one
browser and 136 in the other. When that line appears, this page points at it and says which
browser wrote it and what the other one writes instead. Remove the <head>,
or use method="xml", and the two agree again.
What comes out, and what is done to it on the way
Nothing is re-indented and nothing is normalized. The result is serialized by the method your
stylesheet declared in xsl:output — XML through XMLSerializer, HTML
through the HTML serializer, text as the characters themselves — and there is deliberately no
control on this page to override it, because a picker that silently disagreed with your
xsl:output would make the tester lie about the program it just ran. When no
method is declared, XSLT 1.0 derives one from the result root, and the receipt says which of
the two happened rather than only naming the answer. Copy and Save write the whole string even
when the preview on screen is clipped.
If what you actually want is to read the source document rather than transform it, the
XML formatter indents it and shows its structure. If you need
XML to feed in and what you have is a spreadsheet, CSV to XML makes
one from your rows. And when your method="html" result comes out as one long
line, the HTML formatter lays it out — the browser's HTML
serializer does not indent, which is a property of the serializer and not of your stylesheet.
Where the transform runs, and how long it will keep working
In this tab, start to finish. Neither document is uploaded, there is no endpoint to receive
one, and the document() function cannot reach the network from here — it
resolves to nothing, which is worth knowing before you conclude that your stylesheet is
broken. The XSLT API lives in the document rather than in a worker, measured in both engines,
so a very large pair of documents holds this tab still while it runs; past roughly two million
characters the page says so first and waits for you to press the button, which is a warning
rather than a refusal. Nothing here is rejected for its size.
One thing should be said plainly rather than discovered later. Both browsers now log a
deprecation warning the moment XSLTProcessor is used, and Chrome's names an
intent to remove it. If that day comes, this page will not silently break: it checks for the
engine and tells you it is gone, and points you at xsltproc or Saxon on your own
machine instead of showing you an empty box. A tool built on a capability that is being
withdrawn owes you that much notice.
Questions
Which version of XSLT does this run?
XSLT 1.0 and XPath 1.0 only. The transform is done by the XSLT engine already inside your browser, which is libxslt in both Chrome and Safari, and libxslt implements 1.0. A stylesheet that declares version="2.0" is not rejected for the number, but the moment it uses a 2.0 construct such as xsl:for-each-group or upper-case() the engine fails the whole run, so the page checks for those names and tells you which ones it found.
Do my namespace declarations have to be on the root element?
No. That rule belongs to some server-side testers, not to the browser. It was measured here: a prefix declared on a child element of the source, or on the xsl:template that uses it, transforms exactly the same as one declared on the root, in both Chrome and Safari, with byte-identical output.
Why does the result sometimes show a meta tag I never wrote?
Because output method="html" builds a real HTML document, and a browser document keeps a charset declaration in its head. Chrome writes it as <meta charset="UTF-8"> and Safari as <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">. It is the one place the two browsers disagree about this output, so the page names the line instead of leaving you to find it.
Why is there no line number when my stylesheet fails?
Because the browser does not give one. If the XSL is not well-formed XML the parser reports an exact line and column and you get it. If the XSL parses but the engine refuses it, transformToDocument returns null with no message, no code and no position in both engines. Inventing a position there would be worse than admitting there is none, so the page lists what it could check itself instead.
Is the XML or the stylesheet uploaded?
No. Parsing, transforming and serializing all happen in this tab using an engine that is already part of the browser. There is no request, no temporary file and no log. The document() function cannot fetch anything either — it resolves to nothing here.
Can it turn the result back into the original XML?
No, and no tool can. A stylesheet is a program: it can drop elements, merge them, sort them and compute values, so the output rarely contains enough to reconstruct the input, and never contains the program. Keep the source document; that is the only inverse there is.