XML Date Sorts

Create XSLT Extension Objects to Sort XML Data

XtremeData

LANGUAGES: C#

ASP.NET VERSIONS: 1.1

 

XML Date Sorts

Create XSLT Extension Objects to Sort XML Data

 

By Dan Wahlin

 

Extensible Stylesheet Language Transformations (XSLT) provides a flexible mechanism for sorting numbers and text found in XML documents. However, version 1.0 doesn't support sorting dates with the xsl:sort element. This is unfortunate, because dates are quite common in applications.

 

Consider the XML document shown in Figure 1. Using strictly native XSLT/XPath 1.0 features, it's difficult to sort the Customer nodes based on dates contained in the CustomerSince child nodes, because the xsl:sort element's data-type attribute only supports text and numeric types. In this article, I'll demonstrate how you can get around this sorting issue (without resorting to multiple sorts based on date substrings) by using an XSLT extension object written in C#.

 

  

    ANTON

    ...

    2002-1-3

  

  

     ANATR

    ...

    2002-1-4

  

  

    AROUT

    ...

    2002-1-2

  

  ...

Figure 1: Sample XML document. It's commonly necessary to sort nodes in an XML document based on dates. The extension object demonstrated in this article allows sorting to occur based on the CustomerSince node.

 

.NET XSLT extension objects are compiled classes that provide a powerful way to provide functionality otherwise not available in the XSLT language. By using extension objects, XSLT stylesheets can perform advanced math calculations, tie into data sources other than XML, and perform more common tasks, such as sorting dates.

 

Although extension objects have many advantages, they're not without disadvantages. It's important to keep in mind that XSLT stylesheets that leverage extension objects are less portable across operating systems and development platforms. Of course, this isn't a problem if your stylesheets don't need to be ported to different platforms, but if portability is an issue, you may want to consider using specialized XSLT libraries, such as those found at http://www.exslt.org.

 

XSLT Scripting

Before detailing how to build an extension object, let's take a moment to discuss a viable alternative to extension objects referred to as XSLT scripting. Although it isn't part of the XSLT 1.0 specification, this technique involves embedding C#, VB .NET, or JScript code directly into an XSLT stylesheet. To help with performance, the embedded code is compiled into MSIL the first time it's executed.

 

The code in Figure 2 demonstrates how a C# function named ToDateString can be embedded in an msxsl:script element. The msxsl namespace prefix must be defined within the XSLT stylesheet. It must also be associated with the urn:schemas-microsoft-com:xslt Uniform Resource Identifier (URI). The value specified in the implements-prefix attribute (the DateFunc namespace prefix in this case) can be associated with any namespace URI.

 

  

    public string ToDateString(XPathNodeIterator node)

    {

      node.MoveNext();

      return DateTime.Parse(

        node.Current.Value).ToLongDateString();  

    }

  ]]>

Figure 2: XSLT stylesheets written to run within the .NET Framework can leverage script blocks as shown here. The ToDateString method converts dates such as 1-2-2002 to a friendlier format, such as Wednesday, January 02, 2002.

 

Consider an example of calling the ToDateString method from within an xsl:value-of element, to convert the data within the CustomerSince element to a friendlier date format:

 

  select="DateFunc:ToDateString(CustomerSince)"/>

 

You can see that XSLT scripting makes it easy to extend stylesheet functionality with minimal effort. However, code placed in script blocks is only useful to the XSLT stylesheet in which it's embedded. It can also be more tedious to write, especially when classes outside the System.Xml assembly need to be used. Plus, you don't benefit from IntelliSense as you develop the code. (And let's face it: Many of us are addicted to IntelliSense.) Now that you've been introduced to XSLT scripts, let's move on and discuss the process of creating XSLT extension objects.

 

Sort Dates in XSLT with Extension Objects

Writing XSLT extension object code is no different than writing code for any .NET application. Simply create a class with methods that perform the activities you desire. No inheritance or interface implementation is required. To provide date sorting capabilities for XSLT stylesheets, I created a class named XSLTDateTime that has two methods: SortByDate and ToDateString.

 

Take a look at the complete code for SortByDate (see Figure 3). Notice that the method accepts three parameters: the context node being acted on by the XSLT processor, the node (or nodes) to return from the method after an XPath statement is executed, and the node containing the date data on which the sort will be based.

 

public XPathNodeIterator SortByDate(XPathNodeIterator node,

   string nodeToSelect, string dateNodeName)

{

   string xpath =

    nodeToSelect + "[" + dateNodeName + " != '']";

  // Check if we sort on an attribute or text node.

   string sort = (dateNodeName.IndexOf("@") != -1)?

    dateNodeName:dateNodeName + "/text()";

  // Position nav on first node of node set.

  node.MoveNext();

  XPathNavigator nav = node.Current;

  // Compile XPath expression so we can add a sort to it.

  XPathExpression exp = nav.Compile(xpath);

  DateComparerXSLT dc = new DateComparerXSLT();

  exp.AddSort(sort,dc);

  // Select nodes so we can see the sort.

   return (XPathNodeIterator)nav.Select(exp);

}

Figure 3: The SortByDate method executes an XPath statement that returns one or more nodes sorted based on dates. The method relies on the XPathNavigator class and other helper classes such as XPathExpression and XPathNodeIterator.

 

Once the SortByDate method is called, the node parameter's MoveNext method is called to move to the first node in the node set. This is necessary to get the correct context node being processed by the XSLT processor. The nodes object's Current property is then called to access the underlying XPathNavigator object named nav. The nav object is needed to compile an XPath statement and return an XPathExpression object. It's also needed to execute an XPath statement and locate specific nodes in the source XML document.

 

Because the XPathExpression class' AddSort method doesn't natively support sorting by date, I created another class named DateComparerXSLT that implements the IComparer interface (see Figure 4). This custom object is passed to the AddSort method so that dates can be properly sorted. IComparer contains a single method named Compare that's used to do comparisons between objects. The integer value returned from Compare is used to properly sort data when the XPath statement is executed.

 

public class DateComparerXSLT : IComparer

{

  public DateComparerXSLT() {}

 

  public int Compare(object date1, object date2)

  {

    int intResult;

    DateTime d1 = Convert.ToDateTime(date1);

    DateTime d2 = Convert.ToDateTime(date2);

    intResult = DateTime.Compare(d1,d2);

    return intResult;

  }

}

Figure 4: Performing custom comparisons between objects is accomplished by implementing the IComparer interface. The Compare method returns -1, 0, or 1 depending on how the two objects being compared match up.

 

After the sort is added to the XPathExpression object (named exp) shown earlier in Figure 3, this code is called to execute the XPath statement contained in exp and perform the sort:

 

// Select nodes so we can iterate through them,

// and see the sort.

return (XPathNodeIterator)nav.Select(exp);

 

Using the XSLT Extension Object

To call the SortByDate method from within an XSLT Stylesheet, the code controlling the XSLT transformation must pass the extension object to the stylesheet. To do this, an XslTransform object must be created along with an XsltArgumentList object. The XsltArgumentList has a method named AddExtensionObject that can be used to associate an extension object with a stylesheet. The code in Figure 5 creates these objects and performs the XSLT transformation.

 

string dateType = "LongDate";

StringWriter sw = new StringWriter();

XPathDocument doc = new XPathDocument(

  Server.MapPath("XML/Customers.xml"));

XslTransform trans = new XslTransform();

 

// Load stylesheet and provide evidence so extension

// object can be used.

trans.Load(new XPathDocument(Server.MapPath(

  "XSLT/Customers.xslt")).CreateNavigator(),

  new XmlUrlResolver(), this.GetType().Assembly.Evidence);

 

// Create and add extension object and DateType param.

// DateType param determines how dates are displayed.

XSLTDateTime sorter = new XSLTDateTime();

XsltArgumentList args = new XsltArgumentList();

args.AddExtensionObject(

  "urn:xsltExtension-XSLTDateTime", sorter);

args.AddParam("DateType", "", dateType);

trans.Transform(doc, args, sw, null);

Figure 5: The XsltArgumentList class can be used to add parameter data and extension objects to stylesheets for use in an XSLT transformation.

 

When AddExtensionObject is called, the namespace URI (associated with the extension object namespace prefix defined in the XSLT stylesheet) must be passed along with the actual object instance. The stylesheet knows how to handle the extension object because of the inclusion of a custom namespace URI. Notice that the URI string passed to AddExtensionObject in Figure 5 matches the one defined by the XSLTDateTime namespace prefix shown here:

 

< xsl:stylesheet version="1.0"

   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

   xmlns:XSLTDateTime="urn:xsltExtension-XSLTDateTime">

...

 

Once the extension object is passed to the stylesheet, the XSLTDateTime object's SortByDate method can be called from within the stylesheet to retrieve a node set containing Customer nodes that are sorted based on date data found in the CustomerSince child node (see Figure 6).

 

  

    "XSLTDateTime:SortByDate(.,'Customer','CustomerSince')">

    

      

      

        

          "XSLTDateTime:ToDateString(CustomerSince,$DateType)"/>

      

    

  

Figure 6: Calling an extension object method from within an XSLT stylesheet is accomplished by prefixing the method name with a namespace prefix that's associated with the extension object.

 

The XSLTDateTime extension object contains an additional method named ToDateString that can be called to convert numeric dates to long and short formats (see Figure 7). Output is generated by calling the SortByDate and ToDateString methods from within an XSLT stylesheet (see Figure 8).

 

public string ToDateString(XPathNodeIterator node,

  string dateType)

{

  // Try to convert dateType to DateTypeEnum.

  DateTypeEnum type;

  try

  {

    type = (DateTypeEnum)Enum.Parse(

             typeof(DateTypeEnum),dateType,true);

  }

  catch

  {

    return "Invalid DateTypeEnum type passed. " +

           "You can use 'LongDate' or 'ShortDate'";

  }

  node.MoveNext();  // Position nav on first node.

  XPathNavigator nav = node.Current;

  string val = GetNodeValue(nav);

  switch (type)

  {

    case DateTypeEnum.ShortDate:

      try

      {

        return DateTime.Parse(val).ToShortDateString();

      }

      catch

      {

        return val;

      }

    case DateTypeEnum.LongDate:

      try

      {

         return DateTime.Parse(val).ToLongDateString();

      }

      catch

      {

        return val;

      }

    default:

      return val;

  }

}

Figure 7: The ToDateString method converts a string value of "LongDate" or "ShortDate" to a custom enumeration, and then converts the appropriate date data to the desired output format.

 


Figure 8: The data shown here is sorted based on date data found within a node named CustomerSince (see Figure 1). Notice that in addition to properly sorting by date, the numeric dates found in the XML document have been converted to friendlier string versions of the date.

 

Conclusion

Certainly, XSLT extension objects aren't needed every time XML data is transformed, but they can be quite useful in certain situations. In this article you've seen one potential way to leverage extension objects to sort XML data based on dates. You've also seen how code can be embedded directly into XSLT stylesheet script blocks.

 

Looking to the future of XSLT, in version 2.0 you won't be required to go to a lot of extra effort to sort dates while transforming XML documents because sorting on specific data types defined in XML schemas will be natively supported (as of this writing XSLT 2.0 was a "Working Draft"). Until XSLT 2.0 is released and supported by the .NET Framework, the XSLTDateTime extension object provides a fairly simple way to handle date sorting.

 

The sample code in this article is available for download. To view a live example of the code, visit Dan's XML for ASP.NET Developers Web site: http://www.xmlforasp.net/CodeSection.aspx?csID=104.

 

Dan Wahlin (Microsoft Most Valuable Professional for ASP.NET and XML Web services) is the president of Wahlin Consulting and founded the XML for ASP.NET Developers Web site (http://www.XMLforASP.NET), which focuses on using XML and Web services in Microsoft's .NET platform. He's also a corporate trainer and speaker, and teaches XML and .NET training courses around the United States. Dan coauthored Professional Windows DNA (Wrox, 2000) and ASP.NET: Tips, Tutorials and Code (SAMS, 2001), and authored XML for ASP.NET Developers (SAMS, 2001).

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish