One of the best things about working on MSDN2 was getting to work with my longtime friends Kim and Tim. Well, I see via Chris they've teamed up to release XSLTO (XSL Transform Objects, if I have the abbreviation correct). It's a deceptively simple little bit of code. Although I've talked with Tim about the idea several times over the last few months, this is my first chance to see the code. I think it's an extremely excellent idea, and I'll tell you why: because NAnt sucks.
What? What does a build tool have to do with an object-based XML transformation utility? Well, over the last few years, I've been doing a lot of build script work. And I've come to really like the stuff that NAnt has built in, like named filesets, the solution task, integration with CruiseControl.NET, etc. It has tons of useful utilities for doing most of the things you'd want to do as part of a build, and extension points for doing stuff the NAnt team didn't think of. I like it a lot.
So if I like it so much, why do I say it sucks? I guess I should have been more precise and said that while NAnt the tool is great, NAnt the language sucks. I'm constantly looking in the documentation to remember what the form of various tasks is, and it's a fair bitch to read. More, it's yet another language that my clients have to support when my contract ends and I'm out the door. Frankly, I'd much rather that NAnt-the-language looked like C#, even though it would limit some of the funky things you can do with NAnt. That's not a big deal to me because I rarely do those funky things.
Another language that has this same problem is XSLT. It's really great at the things it's great at (specifically, recursive descent processing of XML), but it also is Yet Another Language for me to remember and support, and I don't get all the nifty tool support I get in C#. I think XSLT is probably one of the reasons people are excited about things like Comega - because they get the natural mapping to XML data types but in a way that's comfortable for C# developers.
Which brings us back to XSLTO. What Tim and Kim have done is to give you the same recursive descent processing that XSLT does so nicely, but all in C#. The core of the idea is captured in the MyTransform class from their sample. I'll reproduce it here in its entirety:
public class MyTransform : Transform {
[Match("/person")]
public void Person(XPathNavigator nav) {
Console.WriteLine(nav.Name);
ApplyTemplates("*");
}
[Match("name")]
public void Name(XPathNavigator nav) {
Console.WriteLine(nav.Name);
ApplyTemplates("text()");
}
[Match("age")]
public void Age(XPathNavigator nav) {
Console.WriteLine(nav.Name);
ApplyTemplates("text()");
}
[Match("text()")]
public void Text(XPathNavigator nav) {
Console.WriteLine(nav.Value);
}
}
If you're familiar with XSLT, the structure should look really familiar: a series of chunks of code (methods in XSLTO, templates in XSLT) that are declaratively matched to pieces of the input document (via the [Match] attribute in XSLTO, via the match attribute in XSLT). In this case, they're just printing out the value of each node, but they could just as easily have used an XmlWriter to emit a transformed version of the input document, just as you'd do in an XSLT. They've even got an ApplyTemplates() function that has the same essential meaning it does in XSLT: “Please continue finding matches and calling the appropriate bits of code.“ When all is said and done, it does exactly what you most often need to do with an XML document: crawl over all the nodes and take some arbitrary action whenever you encounter a certain node.
Will this technique enable all the same things you can do with XSLT? Certainly not. But I've written a fair amount of XSLT in my time, and I'd bet that 80% of it could have been done this way no problem. That figure probably goes up to 90% if they were to make the simple addition of supporting an equivalent of the apply-templates mode attribute - a change I suspect would be nearly trivial. To quote myself from earlier in this posting:
Frankly, I'd much rather that NAnt-the-language looked like C#, even though it would limit some of the funky things you can do with NAnt. That's not a big deal to me because I rarely do those funky things.
Well, substitute “XSLT” for “NAnt” - the argument is the same.
And if that weren't enough, it's impressive that it's already useful, and it's only about 300 lines of code, including comments and sample code! I'm looking forward to seeing where they go with the tool.