XSLT for the strangest reason is the pariah of the workplace. Most developers recognize the benefits of XSLT but for one reason or another choose a much more conventional solution. My goal for this article is to get you up to speed.
What is XSLT?
XSLT stands for “eXtensible Stylesheet Language Transformation”. It is used is to transform an XML Document into another type of document. Some common types of XSL Transformations are …
- XML to HTML
We are going to use this type of transformation to produce HTML-encoded E-Mail notifications for an online retailer.
- XML to XML
XSLT allows for data mapping in B2B transactions. It can convert a 3rd party’s XML data into your own native XML data.
- XML to Text
XSLT is used for publishing XML data into a readable format.
Why use XSLT?
Because it’s way better than most grassroot solutions that I have seen. In addition …
- It is Reusable
XSLT Templates are reuseable across transformations given a constant XML Schema. Different transformations can use the same XSLT Templates as long as the XML structure is constant.
- It has a Large Community Following
There is a huge community following for XSLT. Lots of common transformations are available online.
- It is an Industry Standard
XSLT 1.0 has been around since 1999. It is supported in all major browsers. XSLT 2.0 was introduced in 2007 but isn’t as widely supported.
- It is Extendable
You have the ability to supplement XSLT’s functions with your own.
How to use XSLT
An XSL Transformation requires three things …
- An XML Document containing the data to transform.
- An XSLT Document describing the transformation.
- An application capable of applying the XSL Transformation to the XML Document.
An XSL Document contains one or more Templates. The Templates use XPath expressions to navigate through the mydriad of elements in the XML Document. XSL Control Flow Elements and XSL Conditional Elements supplement the navigation through familiar switch, if-then, and for-each statements.
Sound simple enough? Good. Now let’s dive a little deeper…
The XML Document
An XSL Transformation can only be applied to an XML Document. An XML Document is a structured heriarchial collection of element nodes. Each element can have zero or more attributtes. Below is the XML Document which we will be transforming. It represents an Order for an E-Commerce Retailer …
<Order>, <Customer>, <Addresses>, etc… are all elements.
SaleDate, OrderNumber, FirstName, etc… are all attributes.
<?xml version="1.0" encoding="ISO-8859-1"?> <!-- Processing Instruction. Tells the parser to fire off email.xsl when processing this .xml document. --> <?xml-stylesheet href="templates/emailOrderShipped.xsl" type="text/xsl"?> <Order SaleDate = "2007-11-14T12:01:00" OrderNumber="12345" SalesTax="0.00" Shipping="0.00" GrandTotal="74.86"> <Customer Id = "12345" FirstName="Homer" LastName="Simpson" Email="hsimpson@springfield.com" /> <Addresses> <Address Type="SHIP" FirstName="Homer" LastName="Simpson" Line1="742 Evergreen Terrace" Line2="" City="Springfield" State="IL" Zip="62701" /> <Address Type="BILL" FirstName="Homer" LastName="Simpson" Line1="742 Evergreen Terrace" Line2="" City="Springfield" State="IL" Zip="62701" /> </Addresses> <OrderItems> <OrderItem Sku="12345" Name="White Collared Polo Shirt" Uri="http://www.zappos.com/tommy-bahama-the-emfielder-polo-shirt-white" Status="SHIP" Price="29.99" Quantity="1" Shipping="0.00" Subtotal="29.99" SalesTax="0.00" GrandTotal="29.99"/> <OrderItem Sku="12346" Name="Blue Slacks" Uri="http://www.zappos.com/adidas-climacool-3-stripes-pant-chuy-white" Status="ONORD" Price="32.99" Quantity="1" Shipping="0.00" Subtotal="32.99" SalesTax="0.00" GrandTotal="32.99"/> <OrderItem Sku="12347" Name="Doughnut w/ Sprinkles" Uri="http://www.amazon.com/Vo-Toys-Squeaky-Doughnut-Assorted-styles/dp/B0006G5HYM" Status="ONORD" Price=".99" Quantity="12" Shipping="0.00" Subtotal="11.88" SalesTax="0.00" GrandTotal="11.88"/> </OrderItems> </Order>
order.xml
Generating an XML Document Using .NET
Initially I hand coded the XML Document above. However, most XML Documents are not hand coded – they are generated programatically.
The .NET Framework makes it extremely easy to generate XML. Any class can be serialized to an XML string. Only a class’s public properties are serialized. The .NET Framework allows you to fine tune a class’s serialization using the Decorator Pattern. For example, applying [XmlAttribute] to a class’s public property tells the serializer to render the property as an attribute. The class itself is generated as an element. Other customizations are described here.
This is the .NET class representation of the <Order> element …
public class Order { [XmlAttribute] public DateTime SaleDate { get; set; } [XmlAttribute] public int OrderNumber { get; set; } [XmlAttribute] public decimal SalesTax { get; set; } [XmlAttribute] public decimal Shipping { get; set; } [XmlAttribute] public decimal GrandTotal { get; set; } public List<OrderItem> OrderItems { get; set; } public List<Address> Addresses { get; set; } public Customer Customer { get; set; } public Order() { OrderItems = new List<OrderItem>(); Addresses = new List<Address>(); Customer = new Customer(); } }
Order.cs in ScottsJewels.Extensions.UnitTest
I’ve provided a .NET Extension Method that allows you to serialize any class to XML using the .ToXml() function. Here is the guts of the method…
public static class Extensions { public static string ToXml(this Object obj) { StringBuilder str = new StringBuilder(); using (StringWriter writer = new StringWriter(str)) { XmlSerializer x = new XmlSerializer(obj.GetType()); x.Serialize(writer, obj); } return str.ToString(); } } // Call it like this ... string xml = (new Object).ToXml();
XMLExtensions.cs in ScottsJewels.Extensions
Generating an XML Document using SQL Server
SQL Server will allow you to return a result set in XML. It does a fantastic job handling handling both nested elements and element collections. As a developer you just need to define a mapping between the XML heriarchy and the database value. In situations where the domain model isn’t available as .NET classes SQL Server is the (only) way to go. This script will return XML similar to the XML Document above …
select top 1 o.SaleDate as "Order/@SaleDate", o.OrderId as "Order/@OrderNumber", o.TaxAmount as "Order/@SalesTax", o.ShippingAmount as "Order/@ShippingAmount", o.GrandtotalAmount as "Order/@GrandTotal", m.MemberId as "Order/Customer/@Id" , m.FirstName as "Order/Customer/@FirstName", m.LastName as "Order/Customer/@LastName", m.Email as "Order/Customer/@Email", osa.FirstName as "Order/Addresses/Address/@FirstName", osa.LastName as "Order/Addresses/Address/@LastName", oi.CanceledDate as "Order/OrderItems/OrderItem/@Sku", oi.VariantGroupName as "Order/OrderItems/OrderItem/@Name" ... from [Order] o (nolock), [OrdrItem] oi (nolock), [Member] m (nolock), [OrderBillingAddress] oba (nolock), [OrderShippingAddress] osa (nolock) where m.MemberId = o.MemberId and o.OrderId = osa.OrderId and o.OrderId = oba.OrderId and o.OrderId = oi.OrderId and o.OrderId = 12994625 for xml path('')
Sample SQL Server XML Query
XPath. The XML Navigation Language
You can’t use XSLT without first knowing how to navigate the XML Document. XPath Expressions are integral to every XSL Transformation. An XPath Expression can contain both Operators and Functions.
XPath Operators
These are the most common operators that you will use when navigating an XML Document. I will include some examples given the XML Document listed above. For now don’t worry about the XSL elements – we’ll go over those in a bit …
/Selects a child element.
<xsl:template match="/Order">
This will select all <Order> elements.
../Selects the parent element.
<xsl:template match="/Order/Customer"> <xsl:value-of select="../@SaleDate"/>
This will select the child <Customer> element of an <Order> element. Then retreat back to the <Order> element and render it’s SaleDate attribute.
.Selects the current element.
<xsl:template match="/Order"> <xsl:call-template name="renderOrder"> <xsl:with-param name="order" select="." /> </xsl:call-template>
The <Order> element is sent in to the Order XSL Template.
@Selects the Attribute of an Element.
<xsl:template match="/Order"> <xsl:value-of select="@SaleDate"/>
Will render the <Order> element’s SaleDate attribute.
$Selects the value of a XSL Template Parameter.
<xsl:template name="renderAddress"> <xsl:param name="address" /> <xsl:value-of select="$address/@FirstName"/> ...
Will render the address parameter’s FirstName attribute. Address is likely an element.
[]Queries a collection of Elements.
<xsl:template match="/Order"> <xsl:call-template name="renderAddress"> <xsl:with-param name="address" select="Addresses/Address[@Type='SHIP']"/> </xsl:call-template>
Queries the <Addresses> element for <Address> elements with a Type attribute of “SHIP”.
<xsl:template match="/Order"> <xsl:call-template name="renderAddress"> <xsl:with-param name="address" select="Addresses/Address[0]"/> </xsl:call-template>
Selects the first <Address> element in the <Addresses> element.
XPath Functions
To a lesser degree you will also use XPath Functions in your expressions. I guess this is as good a time as any to mention that XSLT 1.0 does not have any support for Regular Expressions. You will need to leverage these functions as a workaround.
Here is a subset of the functions that XPath offers …
Here are a couple of examples of the two I use the most – format-number() and contains().
<xsl:template match="/Order"> <xsl:value-of select="format-number(@GrandTotal, '$###0.00')"/>
Formats the GrandTotal attributte of the <Order> element as currency.
<xsl:template match="Order/OrderItem[contains('ONORD',@Status)]"> <xsl:value-of select="@Sku"/>
Renders the Sku attribute for <OrderItem> elements that have a Status Attribute of “ONORD”.
Does XPath Support ToUpper() and ToLower()?
No. XSLT 1.0’s string manipulation is lacking. Here is a workaround …
<xsl:template match="Order/OrderItems/OrderItem[0]"> <xsl:value-of select="translate(@Status,'abcdefghijklmnopqurstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
This will render the first <OrderItem>’s Status attribute in upper case.
The unabridged XPath documentation is available on WC3. Here you can learn more than you (probably) ever wanted to know.
The XSL Transformation Document
An XSL Transformation Document describes how an XML Document should be transformed. XSL is a structured language that is no more than a heriarchial collection of elements and attributes. Visually it looks very similar to the XML that it was designed to transform. The top-most element is the <xsl:stylesheet>. A <xsl:stylesheet> element can contain any number of <xsl:template> elements. Control elements such as <xsl:if> and <xsl:for-each> are nested within a <xsl:template> element and are used to navigate through the XML Document’s elements. All XSL elements leverage XPath to describe how the XML Document should be traversed.
The XSL Stylesheet Element
At the root of every XSL Document is an <xsl:stylesheet> element. The stylesheet determines what external files are required by the transformation and what the ultimate output will be – typically XML, HTML, or Text. Within the stylesheet you’ll find one or more <xsl:template> elements.
Here is a simple example of an XSL Transformation…
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html"/> <xsl:template match="/Order"> ... <span id="orderNumber">Order # <xsl:value-of select="@OrderNumber"/></span> <p>Dear <xsl:value-of select="Customer/@FirstName"/><xsl:text> </xsl:text><xsl:value-of select="Customer/@LastName"/>,</p> ...
emailOrderShipped.xsl
The XSL Template Element
An <xsl:template> describes a transformation. The “match” attribute contains an XPath Expression that describes which nodes in the XML Document that the template should be applied to.
Given the example above …
<xsl:template match="/Order">
…specifies that the template should apply to all <Order> elements within the XML Document. All other XSL elements contained within the <xsl:template> would be applied to the XML element selected by the <xsl:Template>. This means that …
<xsl:value-of select="Customer/@FirstName"/>
…would navigate to the <Customer> element within the selected <Order> element and print out it’s FirstName attribute. This may sound trivial but had me stumped for a while so let me clarify. Any navigation performed by an XSL element on an XML Document is carried over to all of the XSL elements nested inside of it.
Invoking an XSL Template from within a Template
An <xsl:template> element can invoke another <xsl:template>. The following example invokes a template named “renderUniversalDate” …
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> ... <xsl:include href="datetime.xsl" /> ... <xsl:output method="html"/> <xsl:template match="/Order"> ... <xsl:call-template name="renderUniveralDate"> <xsl:with-param name="dateTime" select="@SaleDate" /> </xsl:call-template>
emailOrderShipped.xsl
You use <xsl:call-template> to call another <xsl:template> by name. You can send in parameters to the <xsl:template> using <xsl:with-param>. In this example we are sending in the SaleDate attribute of the <Order> element that is selected by the <xsl:template>. An attribute is selected from an XML element by prepending it with a @ symbol.
Below is the <xsl:template> that is being called. The <xsl:template>’s parameters are declared using <xsl:param>. The value of the parameters can be retrieved using the parameter’s name prepended by the $ symbol.
<xsl:template name="renderUniveralDate"> <xsl:param name="dateTime" /> <xsl:variable name="date" select="substring-before($dateTime, 'T')" /> ... </xsl:template>
dateTime.xsl
XSL Templates in External Files
You can call <xsl:template>’s that exist in another file. To do this you just need to use <xsl:include> to reference the external file. For example …
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- Import templates from other files. This must come FIRST in your root XSLT document. --> <xsl:include href="global.xsl" /> <xsl:include href="datetime.xsl" /> <xsl:include href="address.xsl" /> <xsl:include href="enumeration.xsl" /> <xsl:include href="order.xsl" /> ...
emailOrderShipped.xsl
XSL Conditonal Elements
Within an <xsl:template> you will inevitably need to have some control over the processing of the elements returned by your XPath Expressions. XSLT has several elements that support conditional expressions. The most useful ones that I came across are <xsl:if> and <xsl:choose>.
<xsl:if>Allows you to perform an if-then statement.
<xsl:template match="/Order"> <xsl:if test="Addresses/Address[0]/@Line2 != ''"> <xsl:value-of select="/Order/Addresses/Address[0]/@Line2"/> </xsl:if>
This will render The Line2 attribute of the first <Address> element if it is not blank.
<xsl:choose>Allows you to implement a switch statement.
<xsl:template name="renderOrderItemStatus"> <xsl:param name="status" /> <xsl:choose> <xsl:when test="$status = 'SHIP'"> <xsl:text>Shipped</xsl:text> </xsl:when> <xsl:when test="$status = 'ONORD'"> <xsl:text>On Order</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>Unknown</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:template>
This template is called whenever a shipping identifier needs be transalated into a human-readable string. “SHIP translates to “Shipped”. “ONORD” translates to “On Order”. The default value is “Unknown”.
XSL Control Flow Elements
An XML Document can have collections of elements. At some point you are going to need to programatically iterate through these elements. XSLT keeps it simple and only supports one way of doing this – the <xsl:for-each> element.
<xsl:for-each>Allows you to iterate through a collection of XML elements.
<xsl:template match="/Order"> <xsl:for-each select="OrderItems/OrderItem"> <xsl:value-of select="@Sku"/> </xsl:for-each>
This will iterate through each <OrderItem> element in an <Order>. The Sku attribute will be printed for ech <OrderItem>.
XSL Rendering Elements
An XSL Transformation exists solely to transform an XML Document into another document. Transformation is inevitably going to require rendering to the output document.
HTML markup can exist alongside XSL in an XSL-to-HTML Transformation Document. The HTML markup is not processed and is just copied over to the output. If you include HTML markup within an <xsl-for-each> loop the HTML markup will be repeated for each iteration.
In my experience these are the most useful XSL rendering elements …
<xsl:text>This renders literal text to the output document. Any literal text should be contained within an <xsl:text> element.
<xsl:text>Hello World!</xsl:text>
This would just render “Hello World!” to the output document. You do not need to use <xsl:text> when rendering HTML markup.
<xsl:value-of>This renders data selected from the XML document to the output document.
<xsl:template match="/Order"> <xsl:value-of select="OrderItems/OrderItem[0]/@Sku"/>
This renders the Sku attribute of the first <OrderItem> to the output document.
{}Okay, so the curly brackets are not an XSL element! However, they are used occasionally when rendering. Specifically, the curly brackets allow you to dynamically render an element’s attribute value. Kind of a mouthful, huh? Hopefully this example will claify things. Say you want to dynaically specify the href attribute in an <a href> element rendered to the output document.
<xsl:template match="/Order/OrderItems/OrderItem[0]"> <a href="<xsl:value-of select='@Sku'/>"><xsl:value-of select="@Sku"/></a>
This will throw a nasty error.
<xsl:template match="/Order/OrderItems/OrderItem[0]"> <a href="{@Uri}"><xsl:value-of select="@Sku"/></a>
This won’t.
Executing a XSL Transformation
All major Internet Browsers (Firefox, Internet Explorer, Chrome, Safari) and Development Frameworks (Java, and .NET) support XSLT 1.0 natively. There are also several command-line utilities such as xsltproc that can perform XSL Transformations as well.
For quick smoke tests an Internet Browser will suffice. However, for debugging I have found Microsoft’s Visual Studio to be extremely useful. Visual Studio will allow you to debug an XSLT transformation complete with breakpoints. If you plan on automating your XSL transformations then xsltproc, Java, and .NET are your best options.
XSL Transformation using an Internet Browser
Opening an XML Document with an Internet Browser will inevitably lead to the XML Document being displayed in the viewport. To force an XSL Transformation you need to add a Processing Instruction to your XML Document. The Processing Instruction specifies how the XML Document should be processed. A Processing Instruction exists within <? ?>.
This example forces the XML Document to be transformed by the XSL Transformation in emailOrderShipped.xsl …
<!-- Processing Instruction. Tells the parser to fire off email.xsl when processing this .xml document. --> <?xml-stylesheet href="templates/emailOrderShipped.xsl" type="text/xsl"?> <Order SaleDate = "2007-11-14T12:01:00" OrderNumber="12345" ...
order.xml
XSL Transformation using .NET
I’ve provided a .NET Extension method that allows you to apply an XSL Transformation to any class using the .XslTransform() method. The method will first call the .ToXml() method on the targeted object and then transform the resulting XML using the specified XSL Document.
Here is the guts of the method …
public static string XslTransform(this Object obj, string xslTransformFilePath) { string result = string.Empty; if (obj != null) { string xml; xml = obj.ToXml(); if (File.Exists(xslTransformFilePath)) { XslCompiledTransform xslt = new XslCompiledTransform(); xslt.Load(xslTransformFilePath); using (StringReader xmlStream = new StringReader(xml)) { using (StringWriter stringWriter = new StringWriter()) { using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })) { xslt.Transform(XmlReader.Create(xmlStream), xmlWriter); result = stringWriter.ToString(); } } } } } return result; } // Call it like this ... string result = (new Object).XslTransform(xslDocument);
XMLExtensions.cs in ScottsJewels.Extensions
Debugging an XSL Tranformation using Visual Studio
Visual Studio 2010 offers XSLT debugging. If you open an XSL Transformation (.xsl) file in Visual Studio 2010 it will enable an XML option on the file menu …
Selecting the XML option will allow you to chose the .xml file you want to apply the transformation to …
You can debug the XSL Transformation using Visual Studio. Breakpoints and the Watches window are all perfectly functional …
Conclusion
XSL Tranformations are scary on the surface. Hopefully this article has shed some light on the subject and provided enough insight for you to get started.
I have included some sample code that performs a common task : XML-to-HTML Transformations using XSLT. The sample code assumes that you need to produce customer E-Mails from XML. The easiest way to get started with the sample is to open the order.xml document in your Internet Browser.
If you want to get a little dirtier take a look at the ScottsJewels.UnitTests project. This project contains unit tests for the .ToXml() and .XslTransform() .NET Extensions Methods as well as transformations for three different XML-to-HTML transformations.
If you have any questions or comments please let me know!