XML to CSS with XSLT How-To
I set out today to write about converting a simplistic XML document into a simple CSS stylesheet. The specific example I chose to work with creates code for CSS-rollover buttons. The first part I deal with is how to create such a rollover manually. The second part is about generating CSS like this using XSLT and a homemade XML language.

Making the Graphics with the Gimp

I started with the Gimp. First I used the Simple Beveled Button Script to produce a button. After that I made another, but set the upper right colour to be a little brighter. Next I made the same button again, but this time I checked off the "pressed" checkbox. So now I've got three images in the Gimp that represent the three states of the button I want to put on a web page. The first is the default state, the second is used when the mouse is over the button and the third is used when the button is clicked. There's a popular method of making the button change between these states very smoothly using just CSS. Basically you pack all three images into a single image file. This single packed file is just an ordinary image (gif, jpeg or png) that has all three states of the button one next to the other. The picture is set as the background image for some otherwise transparent box. Then the background-offset CSS property is used to adjust the position of the background so that the different parts of the image are visible when the button is in each of the three states I mentioned. Oh, and the three states are distinguished with the help of the CSS pseudo-classes hover and active. All right, time for something more concrete. Here's the kind of background image that we need to make this work. All three button states on one picture Here's how to make this picture from the three button pictures:
  1. Choose File->New in the Gimp, and specify the same width as one of my buttons and three times the height. How did I know the width and height? The Gimp displays the dimensions as width x height in the title bar of each picture.
  2. Flatten each button picture. On each picture, go to the Image menu and choose "Flatten Image".
  3. For the first button state, select the entire image (press ctrl-a). Copy the image (ctrl-c) and paste it into the new packed image (switch to that image window and press ctrl-v). Use the Move tool to position this one exactly at the top. This has to be pixel accurate, so zoom in if you need to (shift + mouse wheel up). I use the keyboard arrow keys for fine adjustments.
  4. Repeat the copy, paste and positioning for the other two pictures, putting them at the middle and bottom of the packed image.
You can also do the compositing step pretty quickly and accurately with ImageMagick in a batch file, but I haven't got time to get into that right now. The point today is supposed to be making XML into CSS.

Testing the CSS by Hand

On to the CSS. Here's an example of the kind of CSS that we need to have in the end: a#cool_button { display:block; width:91px; height:32px; background: url(cool_button.png); } a#cool_button:hover { background-position: 0 -32px; } a#cool_button:active { background-position: 0 -64px; } Applying this CSS gets us a button that looks like this:   when the HTML says <a href="#" id="cool_button">&nbsp;</a> If I hadn't put the text on the button, then I could've just made the text content of the <a /> tag, but it wouldn't be blended in with the graphic.

Building the Template in XSLT

I suppose if all you're interested in is doing buttons one-off, then everything up to here is all you need to know. This has been done before by other people before and with better colour schemes to boot. What I wanted to try out today is something that's purely hypothetical for me right now, but seemed interesting after doing that XSLT-generated code in Lua yesterday. I want to make a simple piece of XSLT that will transform an XML description of a button into the CSS necessary to display the button. I still have to produce the same image file as before, but I can describe the button using some very clear XML notation that suits my needs. Suppose I want some XML that looks like this to describe my buttons: <?xml version="1.0" ?> <buttonset> <button name="cool_button" width=91 height=32 buttonfaces="cool_button.png" /> <button name="hot_button" width=91 height=32 buttonfaces="hot_button.png" /> <button name="panic_button" width=91 height=32 buttonfaces="panic_button.png" /> <button name="push_button" width=91 height=32 buttonfaces="push_button.png" /> </buttonset> Here is some XSLT that will transform this XML into appropriate CSS: <?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text" omit-xml-declaration = "yes" version="1.0" encoding = "UTF-8" /> <xsl:strip-space elements="*" />
<xsl:template match="buttonset"> <xsl:apply-templates /> </xsl:template>
<xsl:template match="button"> <xsl:text>a#</xsl:text><xsl:value-of select="@name" /> <xsl:text > { &#xA;</xsl:text> <xsl:text> display:block;&#xA;</xsl:text> <xsl:text> width:</xsl:text> <xsl:value-of select="@width" /> <xsl:text>px;&#xA;</xsl:text> <xsl:text> height:</xsl:text> <xsl:value-of select="@height" /> <xsl:text>px;&#xA;</xsl:text> <xsl:text> background: url(</xsl:text> <xsl:value-of select="@buttonfaces" /> <xsl:text>);&#xA;</xsl:text> <xsl:text>}&#xA;&#xA;</xsl:text>
<xsl:text>a#</xsl:text><xsl:value-of select="@name" /> <xsl:text>:hover {&#xA;</xsl:text> <xsl:text> background-position: 0px </xsl:text> -<xsl:value-of select="@height" /> <xsl:text>px;&#xA;</xsl:text> <xsl:text>}&#xA;&#xA;</xsl:text>
<xsl:text>a#</xsl:text><xsl:value-of select="@name" /> <xsl:text>:active {&#xA;</xsl:text> <xsl:text> background-position: 0px </xsl:text> -<xsl:value-of select="@height*2" /> <xsl:text>px;&#xA;</xsl:text> <xsl:text>}&#xA;&#xA;</xsl:text> </xsl:template> </xsl:stylesheet>
The XSLT shown here is pretty simple. I've included characters that do nothing but add whitespace to make the output CSS more readable. I tend to leave these out when I'm creating this sort of stuff for my own use. The whitespace is produced by the leading spaces inside the <xsl:text /> elements and the &#xA; (linebreak) trailing characters. The linebreaks are not cross-platform but most editors will handle them correctly. The two templates included match the < buttonset /> and < button /> elements from the source XML file. The first template, for < buttonset />, isn't very interesting. It just applies templates to contained markup. This would be a good place to put any code that goes before or after all the buttons though. The second template, < button />, matches each button element in the source file. It replaces the buttons with the CSS text that we need. The XSLT doesn't understand CSS because it's not an XML-derived syntax. Still, XSLT can produce plain text results and that's the mechanism we're using here. The values of the attributes like height and width are copied into the CSS output by the <xsl:value-of /> elements. The select attribute specifies in most cases just the attribute value to copy, such as @name. In the case of the :active pseudo-class, the offset is twice the height, so I used select="@height*2". I think that statement is always an XPath expression, but I'm not positive. If you run the transform then you'll see that you get a CSS file that looks just like the CSS shown earlier, with additional code for the other three button types. While writing this post, I referred to Sal Mangano's XSLT Cookbook.
Your rating: None

[...] After the little CSS mouseover example I threw in last week, I started thinking about how simple it would be to use the same technique to animate the background-offset value to create the illusion of animation. I know this technique has been done before - I don&#8217;t claim to be the first - but it wasn&#8217;t hard to build my own from where I was at already. The thing that really sucks is that SVG should make this sort of animation easier. Well, this sort and a lot of others. The SVG recommendation from the W3C includes extensive descriptions of support for SMIL, SMIL should be available to handle many kinds of animation. Unfortunately the only way to get SMIL in a browsers is with Adobe SVG Viewer. The fact that it&#8217;s not available in the native browser implementations of SVG means that you cant rely on it as a developer. If you&#8217;re really into declarative animation or want to bank on native SMIL spreading in the future, you can get SmilScript, a clever innovation of Doug Scheppers and Vectoreal. SmilScript will use Javascript to make part of SMIL available to your SVG. [...]

your button image did not display ... this tutorial is just what I've been looking for. thanks

Glad it was helpful to you :)

I just fixed the images too - thanks for the note.

It\'s pretty straightforward to transform an xml document into css with xslt, but were you actually able to apply the resulting css to a 3rd document? In a browser? I\'m curious if you did this/how this would be accomplished.

Yeah, I think back when I wrote this I used Saxon for XSLT most of the time. I'm sure you noticed that in the source of this page I have the CSS for the button styles done as a block up at the top.

If I were to actually implement the sort of thing I talked about in this post on a live site I'd use PHP to do the transform. Then it's a pretty simple matter of passing the name of the xml file to the PHP script in a URL and using that URL as an external stylesheet.

Sort of like this:
&lt;style type="text/css" media="screen" &gt;
@import url(http://www.example.com/styles.php?s=mystyle.xml);

Where styles.php applies the XSLT in my example to mystyle.xml - which could contain the XML from my example. There might be a content type header to pay attention to but in principle it's not too difficult. Of course I've never tried it on a live site so caveat emptor.