Making XML Beautiful Again: Introducing Client-Side XSL
Remember that first time you saw XML and got it? When you really understood what was possible and the deep meaning each element could carry? Now when you see XML, it looks ugly, especially when you navigate to a page of XML in a browser. Well, with every modern browser now supporting XSL 1.0, I’m going to show you how you can turn something as simple as an ATOM feed into a customised page using a browser, Notepad and some XSL.
What on earth is this XSL?
XSL is a family of recommendations for defining XML document transformation and presentation. It consists of three parts:
- XSLT 1.0 – Extensible Stylesheet Language Transformation, a language for transforming XML
- XPath 1.0 – XML Path Language, an expression language used by XSLT to access or refer to parts of an XML document. (XPath is also used by the XML Linking specification)
- XSL-FO 1.0 – Extensible Stylesheet Language Formatting Objects, an XML vocabulary for specifying formatting semantics
XSL transformations are usually a one-to-one transformation, but with newer versions (XSL 1.1 and XSL 2.0) its possible to create many-to-many transformations too. So now you have an overview of XSL, on with the show…
So what do I need?
So to get going you need a browser an supports client-side XSL transformations such as Firefox, Safari, Opera or Internet Explorer. Second, you need a source XML file – for this we’re going to use an ATOM feed from Flickr.com. And lastly, you need an editor of some kind. I find Notepad++ quick for short XSLs, while I tend to use XMLSpy or Oxygen for complex XSL work.
Because we’re doing a client-side transformation, we need to modify the XML file to tell it where to find our yet-to-be-written XSL file. Take a look at the source XML file, which originates from my Flickr photos tagged sky, in ATOM format.
The top of the ATOM file now has an additional <?xml-stylesheet />
instruction, as can been seen on Line 2 below. This instructs the browser to use the XSL file to transform the document.
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="flickr_transform.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:dc="http://purl.org/dc/elements/1.1/">
Your first transformation
Your first XSL will look something like this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<xsl:output method="html" encoding="utf-8"/>
</xsl:stylesheet>
This is pretty much the starting point for most XSL files. You will notice the standard XML processing instruction at the top of the file (line 1). We then switch into XSL mode using the XSL namespace on all XSL elements (line 2). In this case, we have added namespaces for ATOM (line 4) and Dublin Core (line 5). This means the XSL can now read and understand those elements from the source XML.
After we define all the namespaces, we then move onto the xsl:output
element (line 6). This enables you to define the final method of output. Here we’re specifying html
, but you could equally use XML or Text, for example. The encoding attributes on each element do what they say on the tin. As with all XML, of course, we close every element including the root.
The next stage is to add a template, in this case an <xsl:template />
as can be seen below:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/">
<html>
<head>
<title>Making XML beautiful again : Transforming ATOM</title>
</head>
<body>
<xsl:apply-templates select="/atom:feed"/>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
The beautiful thing about XSL is its English syntax, if you say it out loud it tends to make sense.
The /
value for the match attribute on line 8 is our first example of XPath syntax. The expression /
matches any element – so this <xsl:template/>
will match against any element in the document. As the first element in any XML document is the root element, this will be the one matched and processed first.
Once we get past our standard start of a HTML document, the only instruction remaining in this <xsl:template/>
is to look for and match all <atom:feed/>
elements using the <xsl:apply-templates/>
in line 14, above.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/">
<xsl:apply-templates select="/atom:feed"/>
</xsl:template>
<xsl:template match="/atom:feed">
<div id="content">
<h1>
<xsl:value-of select="atom:title"/>
</h1>
<p>
<xsl:value-of select="atom:subtitle"/>
</p>
<ul id="entries">
<xsl:apply-templates select="atom:entry"/>
</ul>
</div>
</xsl:template>
</xsl:stylesheet>
This new template (line 12, above) matches <feed/>
and starts to write the new HTML elements out to the output stream. The <xsl:value-of/>
does exactly what you’d expect – it finds the value of the item specifed in its select
attribute. With XPath you can select any element or attribute from the source XML.
The last part is a repeat of the now familiar <xsl:apply-templates/>
from before, but this time we’re using it inside of a called template. Yep, XSL is full of recursion…
<xsl:template match="atom:entry">
<li class="entry">
<h2>
<a href="{atom:link/@href}">
<xsl:value-of select="atom:title"/>
</a>
</h2>
<p class="date">
(<xsl:value-of select="substring-before(atom:updated,'T')"/>)
</p>
<p class="content">
<xsl:value-of select="atom:content" disable-output-escaping="yes"/>
</p>
<xsl:apply-templates select="atom:category"/>
</li>
</xsl:template>
The <xsl:template/>
which matches atom:entry
(line 1) occurs every time there is a <entry/>
element in the source XML file. So in total that is 20 times, this is naturally why XSLT is full of recursion. This <xsl:template/>
has been matched and therefore called higher up in the document, so we can start writing list elements directly to the output stream. The first part is simply a <h2/>
with a link wrapped within it (lines 3-7). We can select attributes using XPath using @
.
The second part of this template selects the date, but performs a XPath string function on it. This means that we only get the date and not the time from the string (line 9). This is achieved by getting only the part of the string that exists before the T
.
Regular Expressions are not part of the XPath 1.0 string functions, although XPath 2.0 does include them. Because of this, in XSL we tend to rely heavily on the available XML output.
The third part of the template (line 12) is a <xsl:value-of/>
again, but this time we use an attribute of <xsl:value-of/>
called disable output escaping
to turn escaped characters back into XML.
The very last section is another <xsl:apply-template/>
call, taking us three templates deep. Do not worry, it is not uncommon to write XSL which go 20 or more templates deep!
<xsl:template match="atom:category">
<xsl:for-each select=".">
<xsl:element name="a">
<xsl:attribute name="rel">
<xsl:text>tag</xsl:text>
</xsl:attribute>
<xsl:attribute name="href">
<xsl:value-of select="concat(@scheme, @term)"/>
</xsl:attribute>
<xsl:value-of select="@term"/>
</xsl:element>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
In our final <xsl:template/>
, we see a combination of what we have done before with a couple of twists. Once we match atom:category
we then count how many elements there are at that same level (line 2). The XPath .
means ‘self’, so we count how many category
elements are within the <entry/>
element.
Following that, we start to output a link with a rel
attribute of the predefined text, tag
(lines 4-6). In XSL you can just type text, but results can end up with strange whitespace if you do (although there are ways to simply remove all whitespace).
The only new XPath function in this example is concat()
, which simply combines what XPaths or text there might be in the brackets. We end the output for this tag with an actual tag name (line 10) and we add a space afterwards (line 12) so it won’t touch the next tag. (There are better ways to do this in XSL using the last()
XPath function).
After that, we go back to the <xsl:for-each/>
element again if there is another category
element, otherwise we end the <xsl:for-each/>
loop and end this <xsl:template/>
.
A touch of style
Because we’re using recursion through our templates, you will find this is the end of the templates and the rest of the XML will be ignored by the parser. Finally, we can add our CSS to finish up. (I have created one for Flickr and another for News feeds)
<style type="text/css" media="screen">@import "flickr_overview.css?v=001";</style>
So we end up with a nice simple to understand but also quick to write XSL which can be used on ATOM Flickr feeds and ATOM News feeds. With a little playing around with XSL, you can make XML beautiful again.
All the files can be found in the zip file (14k)
About the author
Ian Forrester heads up the BBC’s Backstage, a developer/designer network like no other. He’s well known for geek social events across the capital including London Geekdinners, BarCampLondon and recently the BBC Backstage London Christmas Bash. He’s currently master minding plans BarCampLondon2, a series of backstage social events across the UK and something very special. Somehow, Ian finds time to blog at cubicgarden.com and think about user generated and xml pipelines at his new blog called flow *.