Sunday, September 16, 2007

XSL Class

On several occassions I work with XML data and sometimes binding it to one of the standard ASP.Net controls doesn't quite give you the flexibility necessary to display the data how you need to, especially when dealing with hierarchical data more than a couple of levels deep.

You could always load the XML data and parse through it and build your controls manually; however, sometimes that is hard to do for very complex hierarchies.

In some cases a better solution is XSL.

XSL is a family of recommendations for defining XML document transformation and presentation.

XSL Transformations (XSLT)
A language for transforming XML

XML Path Language (XPath)
An expression language used by XSLT to access or refer to parts of an XML document. (XPath is also used by the XML Linking specification)

XSL Formatting Objects (XSL-FO)
An XML vocabulary for specifying formatting semantics

If you don't use XSL or know much about it and are wanting to learn I highly recommend Michael Kay's XSLT 2nd Edition Programmer's Reference by WROX Books. In my opinion Michael is one of the leading experts on the subject and you'll consistently see him posting on online forums about what XSL is and the proper way to use it, etc.

Over the course of several years I've used XSL in a number of ways such as re-transforming XML data from one format to another, advanced sorting capabilities, or displaying hierarchical data in a very specific format. The powerful capabilities of this language are too large in number to begin to outline or describe in this article; however, one thing that I would like to mention is how you can actually implement an XSL transformation in your code very easily, and in a generic fashion that can be utilized throughout your projects with minimal effort.

The purpose of this class is to centralize many of the things I have come across while using XSL and have them easily accessible and configurable in a reusable class.

This class will let you transform XML data in a file or XML data passed in directly as a string. It could easily be modified to accept the XML as an XMLDocument or other format if needed.

Another interesting thing I want to point out is that the TransformXml function is overloaded to accept either the path of the XSL file or a custom XSL configuration object (XslConfiguration).

Sometimes your code might need to be as simple as passing in XML data and an XSL path and getting back the results:

Using Xsl As New Utilities.Xsl()
    
News.Text = Xsl.TransformXml(Utilities.Xsl.XmlTypes.XmlData, XmlData, Server.MapPath("~/Test.xsl"))
End Using

However sometimes you have more complicated requirements, thus the overloaded version of the TransformXml function can take an XslConfiguration object as a parameter. This allows you to define a number of other things besides the XSL path, including parameters that the XSL transformation needs and an ExtensionObject if you need to execute custom functions from within the transformation.

Here's a similar example using the XslConfiguration object and only specifying an XSL file, no advanced configuration settings:

Using Xsl As New Utilities.Xsl()
    
Using XslConfig As New Utilities.Xsl.XslConfiguration(Server.MapPath("~/Test.xsl"))
         
HtmlData = Xsl.TransformXml(Utilities.Xsl.XmlTypes.XmlData, XmlData, XslConfig)
    
End Using
End Using

And here's another example using the XslConfiguration object with some of the more advanced settings:

Using Xsl As New Utilities.Xsl()
    
Using XslConfig As New Utilities.Xsl.XslConfiguration()
         
'xsl file path
         
XslConfig.XslFile = XslFile

         
'extension object with a function that will be accessible in our transformation
         
XslConfig.ExtensionNameSpace = "urn:ext"
         
XslConfig.ExtensionObject = New ExtensionObject

         
'parameters required in the xsl transformation
         
XslConfig.SetXslParameterNames("Parameter1Name", "Parameter2Name")
         
XslConfig.SetXslParameterValues("Parameter1Value", "Parameter2Value")

         
'transform the data
         
HtmlData = Xsl.TransformXml(Utilities.Xsl.XmlTypes.XmlData, XmlData, XslConfig)

         
'check for errors
         
If Xsl.ErrorDescription <> String.Empty Then
              
HtmlData = xsl.ErrorDescription
         
End If
    
End Using
End Using

Of course for the previous code to work you to define the ExtensionObject class or whatever you chose to name your class, for example:

Private Class ExtensionObject
    
Public Function IsImageAvailable(ByVal Image As String) As String
         
Return File.Exists(HttpContext.Current.Server.MapPath("~/Images/" & Image))
    
End Function
End Class

The actual XSL transformation code could look something like:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ext="urn:ext" version="1.0">
    
<
xsl:output media-type="html" omit-xml-declaration="yes"/>

    
<
xsl:param name="Parameter1Name" />
    
<
xsl:param name="Parameter2Name" />

    
<
xsl:template match="/">
         
Parameter1Name: <xsl:value-of select="$Parameter1Name"/><br/>
         
Parameter2Name: <xsl:value-of select="$Parameter2Name"/><br/>

         
<
xsl:apply-templates select="//Row">
              
<
xsl:sort select="LastName" data-type="text" order="ascending" />
              
<
xsl:sort select="FirstName" data-type="text" order="ascending" />
         
</
xsl:apply-templates>     </xsl:template>
     <xsl:template match="Row">
         
<
xsl:variable name="ShowImage" select="ext:IsImageAvailable(Image)"/>
         
         
<
xsl:choose>
              
<
xsl:when test="$ShowImage='True'">
                   
<
img src="Images/{Image}" />
              
</
xsl:when>
              
<
xsl:otherwise>
                   
<
img src="Images/ImageNotAvailable.jpg" />
              
</
xsl:otherwise>
         
</
xsl:choose>
    
</
xsl:template>
</
xsl:stylesheet>

Notice how we have our xmlns:ext attribute declared in the xsl:stylesheet node. That is what ties our extension namespace back to what we specified in the code-behind:

XslConfig.ExtensionNameSpace = "urn:ext"

And we've defined our 2 parameters that we passed in.

It also demonstrates how we can call our custom function from our extension object, like so:

<xsl:variable name="ShowImage" select="ext:IsImageAvailable(Image)"/>

That's all there is to having a custom XSL class that can be reused for a lot of different scenarios!

0 comments: