Tomasz Wegrzanowski
Posted on December 13, 2021
In the late 1990s and early 2000s there was an XML craze. People even wanted to replace HTML with some XML variant, and literally the only "advantage" it would have over HTML was that if you made any typos, the website would just refuse to display anything at all. Somehow that was supposed to be a huge selling point.
Eventually common sense prevailed, but back then XML craze was going so hot, people were asking questions like - what if I need to turn XML into XML? I know, I'l use XML! That's how XSLT came to be.
Hello, World!
We can't really do conventional Hello, World!, as the whole XSLT model is turning XML into XML, but let's do something simple anyway.
Here's hello.xml
:
<?xml version="1.0" ?>
<persons>
<person>
<name>Alice</name>
</person>
<person>
<name>Bob</name>
</person>
</persons>
And here's hello.xslt
:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/persons">
<messages>
<xsl:apply-templates select="person"/>
</messages>
</xsl:template>
<xsl:template match="person">
<message>Hello, <xsl:value-of select="name" />!</message>
</xsl:template>
</xsl:stylesheet>
We can then run it like this, xsltproc
is even preinstalled on OSX:
$ xsltproc hello.xslt hello.xml
<?xml version="1.0"?>
<messages>
<message>Hello, Alice!</message>
<message>Hello, Bob!</message>
</messages>
So what's going on:
- first, the
<?xml>
boilerplate and some namespaces and versions. It's best to just copy paste paste it. -
xsl:output
specifies output mode, in this case we want to generate XML and indent it automatically for readability. Not every kind of XML should be indented like that. - Then we have two templates with
xsl:template
- top level one for/persons
and then second one for each/person
.
If this seems to you like a bit crazy way to code, then you're not wrong.
Text output
In addition to generating XML, XSLT can also generate HTML and plain text. Let's try some plain text. We need to be very careful to get all the spaces and newlines in the right places, so this look extremely verbose.
Here's text.xml
:
<?xml version="1.0" ?>
<persons>
<person>
<name>Alice</name>
<surname>Cooper</surname>
</person>
<person>
<name>Bob</name>
<surname>Smith</surname>
</person>
</persons>
And here's text.xslt
:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="/persons"><xsl:apply-templates select="person"/></xsl:template>
<xsl:template match="person">
<xsl:text>Hello, </xsl:text>
<xsl:value-of select="name" />
<xsl:text> </xsl:text>
<xsl:value-of select="surname" />
<xsl:text>! </xsl:text>
</xsl:template>
</xsl:stylesheet>
And the output:
Hello, Alice Cooper!
Hello, Bob Smith!
FizzBuzz
We could just generate the whole thing from scratch, but I think it's more true to the purpose of XSLT if we start with this fizzbuzz.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<fizzbuzz>
<number>1</number>
<number>2</number>
<number>3</number>
<number>4</number>
...
<number>100</number>
</fizzbuzz>
Then we could do this for fizzbuzz.xslt
:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="/fizzbuzz"><xsl:apply-templates select="number"/></xsl:template>
<xsl:template match="number">
<xsl:variable name="i" select="." />
<xsl:choose>
<xsl:when test="$i mod 15 = 0">FizzBuzz</xsl:when>
<xsl:when test="$i mod 3 = 0">Fizz</xsl:when>
<xsl:when test="$i mod 5 = 0">Buzz</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$i" />
</xsl:otherwise>
</xsl:choose>
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>
Which generates exactly the FizzBuzz sequence you're expecting.
There's things happenings here:
-
xsl:variable
sets a local variablei
-
xsl:choose
withxsl:whene
andxsl:otherwise
decide which FizzBuzz branch to take - there's also
xsl:if
we could use instead
Loops
XSLT went through many iterations. XSLT 2.0 would actually make this reasonably easy, thanks to more flexible xsl:for-each
, but the XSLT processor that comes with OSX only supports XSLT 1.0, and it's not the only one - a lot of XSLT software never went past XSLT 1.0. So let's give it a go - we don't have loops, but we have recursion.
Basically we first figure out how many iterations we want, then call iteration(1, 20)
. It will then check if current index reached max - if yes, that will be the end of it, otherwise it will call iteration(2, 20)
, which will call iteration(3, 20)
and so on until iteration(20, 20)
eventually stops.
Here's loop.xml
:
<?xml version="1.0" ?>
<loop>20</loop>
And loop.xslt
:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template name="iteration">
<xsl:param name="i" />
<xsl:param name="max" />
<xsl:text>Iteration </xsl:text>
<xsl:value-of select="$i"/>
<xsl:text> </xsl:text>
<xsl:if test="$max > $i">
<xsl:call-template name="iteration">
<xsl:with-param name="i" select="$i + 1"/>
<xsl:with-param name="max" select="$max"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="/loop">
<xsl:call-template name="iteration">
<xsl:with-param name="i" select="1"/>
<xsl:with-param name="max" select="."/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
Which generates:
$ xsltproc loop.xslt loop.xml
Iteration 1
Iteration 2
Iteration 3
Iteration 4
Iteration 5
Iteration 6
Iteration 7
Iteration 8
Iteration 9
Iteration 10
Iteration 11
Iteration 12
Iteration 13
Iteration 14
Iteration 15
Iteration 16
Iteration 17
Iteration 18
Iteration 19
Iteration 20
Fibonacci
And now that we can loop, we can generate the Fibonacci sequence.
fib.xml
is just the max value:
<?xml version="1.0" ?>
<fib-sequence>20</fib-sequence>
And let's do fib.xslt
:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template name="fib">
<xsl:param name="n"/>
<xsl:choose>
<xsl:when test="2 >= $n">1</xsl:when>
<xsl:otherwise>
<xsl:variable name="a">
<xsl:call-template name="fib">
<xsl:with-param name="n" select="$n - 1"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="b">
<xsl:call-template name="fib">
<xsl:with-param name="n" select="$n - 2"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$a + $b"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="iteration">
<xsl:param name="i" />
<xsl:param name="max" />
<xsl:text>fib(</xsl:text>
<xsl:value-of select="$i"/>
<xsl:text>) = </xsl:text>
<xsl:call-template name="fib">
<xsl:with-param name="n" select="$i"/>
</xsl:call-template>
<xsl:text> </xsl:text>
<xsl:if test="$max > $i">
<xsl:call-template name="iteration">
<xsl:with-param name="i" select="$i + 1"/>
<xsl:with-param name="max" select="$max"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="/fib-sequence">
<xsl:call-template name="iteration">
<xsl:with-param name="i" select="1"/>
<xsl:with-param name="max" select="."/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
In order:
- we define recursive function
fib(n)
for calculating the Fibonacci value - we define
iteration(i, max)
which will do our looping - we call
iteration(1, max)
at top level/fib-sequence
And the output is as expected:
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(6) = 8
fib(7) = 13
fib(8) = 21
fib(9) = 34
fib(10) = 55
fib(11) = 89
fib(12) = 144
fib(13) = 233
fib(14) = 377
fib(15) = 610
fib(16) = 987
fib(17) = 1597
fib(18) = 2584
fib(19) = 4181
fib(20) = 6765
XSLT 2.0 would make it slightly more readable as we wouldn't ned recursive looping, but in the end it would still be quite dreadful.
Should you use XSLT?
Absolutely not.
XSLT is basically a joke language, except unlike with Emojicode, Befunge, Brainfuck, and such, people who created it weren't in on the joke.
Just about every real language does XML processing better than XSLT. Just pick your favorite.
Usually Ruby or Python is a close call, but in this case the first choice is very clearly Ruby. Ruby's Nokogiri is nearly perfect, and for some reason all Python's XML libraries I've tried (and I've tried a lot of them), had a lot of issues. Of course that's just relatively speaking, any of Python's libraries is still far better than using XSLT.
There are no excuses to use XSLT. It's unsuitable for any purpose, in any version.
Code
All code examples for the series will be in this repository.
Posted on December 13, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.