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:outputspecifies 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/personsand 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:variablesets a local variablei -
xsl:choosewithxsl:wheneandxsl:otherwisedecide which FizzBuzz branch to take - there's also
xsl:ifwe 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.
Top comments (0)