Moving Away From Xslt
20 September 2003
All of this site is written in simple XML documents and transformed to HTML. I find this works really well, and means I never have to worry about dealing with HTML formats. (Not that fancy layout is my style, as you can tell.) I've even written a whole book that way.
For most of this time I've used XSLT as my transformation language. I've got pretty good with slinging XSLT around and getting it to do what I want.
But no more.
When I wrote the software for this Bliki (on a long flight) I did it in Ruby.Prior to that I used Ruby to do a new version of my home page. My conclusion from this exercise was that using Ruby for XML transforms was much easier than using XSLT.
- XML makes a lousy syntax for a programming language. There's way too much noise in there and as a result you can't see the program.
- XSLT makes calling subroutines so painful that you are seriously discouraged from using them, which encourages duplicate code.
- XSLT handles simple tasks well, but is baroque when it comes to more complicated things. Indeed some are impossible and you have to jump out into another language anyway.
- Ruby gives me a clean, OO language with clear syntax and a kick-ass XML library.(Python may well be just as good, I haven't tried it.)
- I can mix template style code with transformer style code.
The design of XSLT does influence the way I use the program. My basic transform tasks are handled using recursive apply functions just like XSLT's apply-template command. In this case I use ruby's reflection to make it work well. The guts of the transformer is
class ElementHandler def apply anElement anElement.each {|e| handle(e)} if anElement end def handle aNode if aNode.kind_of? REXML::Text handleTextNode(aNode) elsif aNode.kind_of? REXML::Element handle_element aNode else return #ignore comments and processing instructions end end def handle_element anElement handler_method = “handle_” + anElement.name.tr(“-”,”_”) if self.respond_to? handler_method self.send(handler_method, anElement) else default_handler(anElement) end end ...
Essentially the handle_element
method uses
reflection to invoke a properly named function in the handler
object. I subclass the ElementHandler for specific kinds of
page. So I have a question tag for the questions that appear on
certain pages. For this I write a short transformation routine.
def handle_question anElement @html.p {@html.element('b'){apply anElement}} end
The @html.element
method pumps out b tags into the output. Inside
those b tags are the result of executing the block of code {apply
anElement}
which continues the recursion. Here ruby's blocks come in
very useful.
There are probably tons of ways to improve on this, but I haven't needed to touch the code much since I made the bliki live. Where I have I've found it much easier to deal with than the XSLT.
I think this may raise some real questions about XSLT. There's still much I like about the power of XSLT, but I hate the syntax and the walls you keep running into. I'm not going to convert my whole site over to Ruby tomorrow - most of the XSLT works fine - but I can certainly see my way to doing that at some point in the future. But the bigger question is whether you're better off with scripting language for this kind of task than XSLT.