I guess a quick introduction wouldn't be completely out of order for a first post. My name is Don Smith and I've been a Dev Consultant at Microsoft in Charlotte, NC for just over 5 years now. I spend most of my time helping our customers to be successful with Microsoft's XML technologies (XSLT, XSD, etc.), Web Services, and distributed architectures. I've been blogging for several years, but I'm just now deciding to join the MSDN blog party. Would love to hear from you if you feel so moved. :-)
Refactoring XSLT is a concept that originally dawned on me while delivering an XML development workshop earlier this year. Here's a touch of background. According to Martin Fowler, Refactoring is improving the design of existing code. With that in mind, one of the most common uses of XSLT is to transform XML data into HTML. Many enterprise web sites are divided [roughly] into 3 sections: the header, the main content, and the footer. If you've even seen or developed an XSLT stylesheet that results in HTML and has a considerable amount of code in the header and footer sections (or perhaps a non-trivial navigation system) you know the spaggetti code (mixture of code and HTML) required to get there is remnicient of Classic ASP. I'm not proposing there is a way to completely avoid the XSLT/HTML coupling, but this method can trim up your XSLT code and apply some structure to your solution.
The principles behind this approach are based on stylesheet extensibility (<xsl:include />) and factoring out large parts of the HTML into their own files. To illustrate this concept I'll use the following files:
Below are enough files to get the idea of how this approach works ...
Data.xml - nothing special here; just a simple XML document
<?xml version="1.0" encoding="utf-8" ?> <contributors xmlns="urn:fund-raiser"> <volunteer> <name>Joe Blow</name> <amount>58.25</amount> </volunteer> <volunteer> <name>John Doe</name> <amount>81.50</amount> </volunteer> <volunteer> <name>Jane Blow</name> <amount>89.75</amount> </volunteer> <volunteer> <name>Julie Doe</name> <amount>96.00</amount> </volunteer> </contributors>
Content.xslt - as you can see, this file's sole function is to produce the main conent for the page (the table). Its call to the Header template will resolve to the Header.xslt include and likewise for the footer. The Common.xslt isn't used by anything in this file. Rather, it will be used by the header and footer (or any other child) stylesheets.
<?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet version="1.0" xmlns:v="urn:fund-raiser" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:include href="Common.xslt" /> <xsl:include href="Header.xslt" /> <xsl:include href="Footer.xslt" /> <xsl:template match="/"> <xsl:call-template name="Header" /> <table width="250"> <xsl:apply-templates select="v:contributors/v:volunteer"/> </table> <xsl:call-template name="Footer" /> </xsl:template> <xsl:template match="v:volunteer"> <tr> <td width="50%"><xsl:value-of select="v:name" /></td> <td width="50%"><xsl:value-of select="v:amount" /></td> </tr> </xsl:template> </xsl:stylesheet>
Header.xslt - all of the header HTML data is in an external file defined in Config.xml. The path is retrieved and passed into the FileContents template as a parameter. The FileContents template will resolve to Common.xslt.
<?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template name="Header"> <xsl:variable name="headerfile" select="document('Config.xml')/configuration/header" /> <xsl:call-template name="FileContents"> <xsl:with-param name="filepath" select="$headerfile" /> </xsl:call-template> </xsl:template> </xsl:stylesheet>
Common.xslt - this is the only part of the solution I'm not crazy about: the script. Granted, even if you're not using .NET, most popular XSLT implementations have similar extension facilities. As you can see below, the FileContents template calls the GetContents function to retrieve the contents of the header. The XPath document() function can't be used here because the header and footer files don't contain well-formed markup.
<?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet version="1.0" xmlns:fac="urn:factoring-xslt" xmlns:msx="urn:schemas-microsoft-com:xslt" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <msx:script language="c#" implements-prefix="fac"> public string GetContents( string filepath ) { try { using( System.IO.TextReader reader = System.IO.File.OpenText( filepath ) ) { return reader.ReadToEnd(); } } catch( System.Exception ex ) { return ex.Message; } } </msx:script> <xsl:template name="FileContents"> <xsl:param name="filepath" /> <xsl:value-of select="fac:GetContents( $filepath )" disable-output-escaping="yes" /> </xsl:template> </xsl:stylesheet>
If you want to play around with these files, feel free to download my sample solution here. I've never seen this approach used anywhere and I've never seen anyone else mention it. I thought a purely original post would be a good start on MSDN. So, if you decide to implement any of these ideas, please test thoroughly. I can't stress that enough. Here are some other resources you might find useful:
If you have any thoughts about this stuff, I'd love to hear 'em. Happy coding!