100 Languages Speedrun: Episode 22: XSLT
Tags: xml, xslt
Published: 2021-12-13 00:49:59

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
<?xml version="1.0" ?>
<persons>
  <person>
    <name>Alice</name>
  </person>
  <person>
    <name>Bob</name>
  </person>
</persons>
```

And here's `hello.xslt`:

```xml
<?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:

```xml
$ 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
<?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
<?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>!&#10;</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
<?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
<?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>&#10;</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 variable `i`
* `xsl:choose` with `xsl:whene` and `xsl: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
<?xml version="1.0" ?>
<loop>20</loop>
```

And `loop.xslt`:

```xml
<?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>&#10;</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
<?xml version="1.0" ?>
<fib-sequence>20</fib-sequence>
```

And let's do `fib.xslt`:

```xml
<?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>&#10;</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](https://github.com/taw/100-languages-speedrun).

[Code for the XSLT episode is available here](https://github.com/taw/100-languages-speedrun/tree/master/episode-22-xslt).