CSS Within XSLT - a way to simplify simple HTML generation from XSLT
It can be a challenge to keep CSS files up to date as you change XSLT transformations to produce different HTML output. You've got to remember what you changed and then work out which CSS rules are affected and update them. Removing out-of-date rules is important, and can even affect your site’s ranking in search results, but it’s hard to do and requires attention to detail.
Recently I’ve been taking a different approach and I’m finding it much more productive.
I call the method CSS Within (OK, CSS Inside sounded too much like a trademark). It's a result of thinking about the single largest flaw I see in Knuth’s Literate Programming: it assumes that programmers like writing and will keep documentation up to date. I don’t know about you but I just jump straight to the function and change it in place without taking time to read a bunch of stuff that doesn’t affect how it works.
So, for XSLT, I wanted to move the CSS into the stylesheet, but putting it before each template was a non-starter because I knew I wouldn't edit it. I wanted the CSS within the template itself.
Here’s an example from a stylesheet I was editing just now:
<css:rule match="div.index p.prevnext">
margin-top: -18pt; /* allow for the height of the uparrow */
margin-bottom: 1px; /* difference between arrow height and ls */
<xsl:with-param name="small" select="true()" />
So now i get the CSS styles right where I need them. I can put them anywhere, but if i put them right after the element constructor, it's super obvious what's going on. I can find them when i need to and i can edit them, and if i decide to delete that p element, and prevnext isn't used anywhere else in the XSLT stylesheet, hey, i just delete the CSS block too. Done.
But what’s really going on here? That css:rule element is actually, of course, an XSLT direct element constructor. It makes an element of that name in the output. Which is not what i want to end up with at all.
Instead, what i want is to gather up all of the CSS rules at the end of processing and write them out to a file. So I have two ways to handle them to achieve this.
The first way is to declare css:rule as an XSLT extension element; i wrote a Java class for Saxon which simply ignores the css:* elements, turning them into an empty sequence when the stylesheet is compiled. That way they don’t even slow down processing.
Then, i have a stylesheet that reads the XSLT stylesheet itself and fishes out the CSS elements and puts them in a CSS file. Note that i want all of the CSS elements, not just the ones in templates that were reached in any one particular document. It’s as easy as adding
"post-process" : css:writefile#2,
If extensions are not supported, i also have an XSLT stylesheet that can load the main stylesheet, strip out the CSS elements, and then continue processing.
Notice also that this markup doesn’t need curly braces, so the CSS doesn’t interfere with text value templates (expand-text=yes) in XSLT. There are CSS elements for media queries, too, which can contain css:rule elements. It’s simpler to do than to describe.
What do you think? Would you like to try it?