XSLT (Extensible Stylesheet Language Transformations)
XSLT (Extensible Stylesheet Language Transformations) is a language for transforming XML documents into other formats including HTML, plain text, or different XML structures. XSLT uses a template-based approach with XPath expressions to select and manipulate XML data.
XSLT Basics
Basic XSLT Structure
Every XSLT stylesheet has this basic structure:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Templates go here -->
</xsl:stylesheet>
Sample XML Document
We'll transform this XML throughout our examples:
<?xml version="1.0" encoding="UTF-8"?>
<library>
<book id="1" category="fiction">
<title>To Kill a Mockingbird</title>
<author>Harper Lee</author>
<year>1960</year>
<price>12.99</price>
<description>A gripping tale of racial injustice and childhood innocence.</description>
</book>
<book id="2" category="science">
<title>A Brief History of Time</title>
<author>Stephen Hawking</author>
<year>1988</year>
<price>15.99</price>
<description>An exploration of the universe and our place in it.</description>
</book>
<book id="3" category="fiction">
<title>1984</title>
<author>George Orwell</author>
<year>1949</year>
<price>13.99</price>
<description>A dystopian social science fiction novel.</description>
</book>
</library>
Templates and Pattern Matching
Basic Template
<xsl:template match="library">
<html>
<head><title>Library Catalog</title></head>
<body>
<h1>Book Collection</h1>
<xsl:apply-templates select="book"/>
</body>
</html>
</xsl:template>
<xsl:template match="book">
<div class="book">
<h2><xsl:value-of select="title"/></h2>
<p>by <xsl:value-of select="author"/></p>
<p>Published: <xsl:value-of select="year"/></p>
<p>Price: $<xsl:value-of select="price"/></p>
</div>
</xsl:template>
Template Priority
<!-- More specific template has higher priority -->
<xsl:template match="book[@category='fiction']">
<div class="fiction-book">
<xsl:apply-templates select="title"/>
<span class="genre">Fiction</span>
</div>
</xsl:template>
<!-- General template for all books -->
<xsl:template match="book">
<div class="book">
<xsl:apply-templates select="title"/>
</div>
</xsl:template>
Core XSLT Elements
xsl:value-of
Extract text content from nodes:
<xsl:template match="book">
<p>Title: <xsl:value-of select="title"/></p>
<p>Author: <xsl:value-of select="author"/></p>
<p>ID: <xsl:value-of select="@id"/></p>
</xsl:template>
xsl:for-each
Iterate over node sets:
<xsl:template match="library">
<table>
<tr><th>Title</th><th>Author</th><th>Year</th></tr>
<xsl:for-each select="book">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="author"/></td>
<td><xsl:value-of select="year"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
xsl:if
Conditional processing:
<xsl:template match="book">
<div>
<h3><xsl:value-of select="title"/></h3>
<xsl:if test="price < 15">
<span class="sale">On Sale!</span>
</xsl:if>
<xsl:if test="@category = 'fiction'">
<span class="badge">Fiction</span>
</xsl:if>
</div>
</xsl:template>
xsl:choose
Multiple conditional branches:
<xsl:template match="book">
<div>
<h3><xsl:value-of select="title"/></h3>
<xsl:choose>
<xsl:when test="price < 12">
<span class="price-low">Budget Pick</span>
</xsl:when>
<xsl:when test="price > 15">
<span class="price-high">Premium</span>
</xsl:when>
<xsl:otherwise>
<span class="price-medium">Regular Price</span>
</xsl:otherwise>
</xsl:choose>
</div>
</xsl:template>
Variables and Parameters
Variables
<xsl:template match="library">
<xsl:variable name="totalBooks" select="count(book)"/>
<xsl:variable name="fictionBooks" select="count(book[@category='fiction'])"/>
<div>
<p>Total books: <xsl:value-of select="$totalBooks"/></p>
<p>Fiction books: <xsl:value-of select="$fictionBooks"/></p>
<p>Non-fiction: <xsl:value-of select="$totalBooks - $fictionBooks"/></p>
</div>
</xsl:template>
Parameters
<xsl:template match="book">
<xsl:param name="showPrice" select="'true'"/>
<div class="book">
<h3><xsl:value-of select="title"/></h3>
<p>by <xsl:value-of select="author"/></p>
<xsl:if test="$showPrice = 'true'">
<p>Price: $<xsl:value-of select="price"/></p>
</xsl:if>
</div>
</xsl:template>
<!-- Call template with parameter -->
<xsl:apply-templates select="book">
<xsl:with-param name="showPrice" select="'false'"/>
</xsl:apply-templates>
Sorting and Grouping
Sorting
<xsl:template match="library">
<div>
<h2>Books by Year (Newest First)</h2>
<xsl:for-each select="book">
<xsl:sort select="year" data-type="number" order="descending"/>
<div>
<xsl:value-of select="year"/>: <xsl:value-of select="title"/>
</div>
</xsl:for-each>
</div>
</xsl:template>
Multiple Sort Criteria
<xsl:for-each select="book">
<xsl:sort select="@category" data-type="text"/>
<xsl:sort select="author" data-type="text"/>
<xsl:sort select="year" data-type="number" order="descending"/>
<!-- Process sorted books -->
</xsl:for-each>
Functions and Expressions
String Functions
<xsl:template match="book">
<div>
<!-- Uppercase title -->
<h3><xsl:value-of select="translate(title, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/></h3>
<!-- Substring -->
<p>Author initials: <xsl:value-of select="substring(author, 1, 1)"/></p>
<!-- String length -->
<p>Title length: <xsl:value-of select="string-length(title)"/> characters</p>
<!-- Contains function -->
<xsl:if test="contains(title, 'Time')">
<span>Time-related book</span>
</xsl:if>
</div>
</xsl:template>
Numeric Functions
<xsl:template match="library">
<div>
<p>Total books: <xsl:value-of select="count(book)"/></p>
<p>Average price: $<xsl:value-of select="format-number(sum(book/price) div count(book), '#.00')"/></p>
<p>Most expensive: $<xsl:value-of select="format-number(book/price[not(. < ../book/price)], '#.00')"/></p>
</div>
</xsl:template>
Advanced Techniques
Named Templates
<xsl:template name="formatPrice">
<xsl:param name="price"/>
<xsl:param name="currency" select="'USD'"/>
<span class="price">
<xsl:choose>
<xsl:when test="$currency = 'USD'">$</xsl:when>
<xsl:when test="$currency = 'EUR'">€</xsl:when>
<xsl:otherwise>¤</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="format-number($price, '#.00')"/>
</span>
</xsl:template>
<!-- Call named template -->
<xsl:call-template name="formatPrice">
<xsl:with-param name="price" select="price"/>
<xsl:with-param name="currency" select="'EUR'"/>
</xsl:call-template>
Keys for Efficient Lookups
<xsl:key name="books-by-category" match="book" use="@category"/>
<xsl:template match="library">
<div>
<h2>Fiction Books</h2>
<xsl:for-each select="key('books-by-category', 'fiction')">
<p><xsl:value-of select="title"/> by <xsl:value-of select="author"/></p>
</xsl:for-each>
</div>
</xsl:template>
Mode Attribute
<!-- Summary mode -->
<xsl:template match="book" mode="summary">
<li><xsl:value-of select="title"/> (<xsl:value-of select="year"/>)</li>
</xsl:template>
<!-- Detailed mode -->
<xsl:template match="book" mode="detailed">
<div class="book-detail">
<h3><xsl:value-of select="title"/></h3>
<p><xsl:value-of select="description"/></p>
<p>Author: <xsl:value-of select="author"/></p>
<p>Year: <xsl:value-of select="year"/></p>
<p>Price: $<xsl:value-of select="price"/></p>
</div>
</xsl:template>
<!-- Usage -->
<ul><xsl:apply-templates select="book" mode="summary"/></ul>
<div><xsl:apply-templates select="book[1]" mode="detailed"/></div>
Complete Example: HTML Catalog
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" doctype-public="-//W3C//DTD HTML 4.01//EN"/>
<!-- Root template -->
<xsl:template match="/">
<html>
<head>
<title>Library Catalog</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.book { border: 1px solid #ccc; margin: 10px 0; padding: 15px; border-radius: 5px; }
.fiction { background-color: #f0f8ff; }
.science { background-color: #f0fff0; }
.price { font-weight: bold; color: #007700; }
.expensive { color: #cc0000; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<xsl:apply-templates select="library"/>
</body>
</html>
</xsl:template>
<!-- Library template -->
<xsl:template match="library">
<h1>Library Book Catalog</h1>
<!-- Summary statistics -->
<div class="summary">
<h2>Collection Summary</h2>
<p>Total books: <xsl:value-of select="count(book)"/></p>
<p>Fiction books: <xsl:value-of select="count(book[@category='fiction'])"/></p>
<p>Science books: <xsl:value-of select="count(book[@category='science'])"/></p>
<p>Average price: $<xsl:value-of select="format-number(sum(book/price) div count(book), '#.00')"/></p>
</div>
<!-- Books table -->
<h2>Complete Listing</h2>
<table>
<tr>
<th>Title</th>
<th>Author</th>
<th>Year</th>
<th>Category</th>
<th>Price</th>
</tr>
<xsl:for-each select="book">
<xsl:sort select="@category"/>
<xsl:sort select="title"/>
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="author"/></td>
<td><xsl:value-of select="year"/></td>
<td><xsl:value-of select="@category"/></td>
<td>
<xsl:choose>
<xsl:when test="price > 15">
<span class="expensive">$<xsl:value-of select="price"/></span>
</xsl:when>
<xsl:otherwise>
<span class="price">$<xsl:value-of select="price"/></span>
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:for-each>
</table>
<!-- Detailed book cards -->
<h2>Featured Books</h2>
<xsl:apply-templates select="book"/>
</xsl:template>
<!-- Book template -->
<xsl:template match="book">
<div>
<xsl:attribute name="class">
book <xsl:value-of select="@category"/>
</xsl:attribute>
<h3><xsl:value-of select="title"/></h3>
<p><strong>Author:</strong> <xsl:value-of select="author"/></p>
<p><strong>Published:</strong> <xsl:value-of select="year"/></p>
<p><strong>Category:</strong> <xsl:value-of select="@category"/></p>
<p><strong>Price:</strong> $<xsl:value-of select="price"/></p>
<p><strong>Description:</strong> <xsl:value-of select="description"/></p>
<xsl:if test="year < 1960">
<p><em>Classic Literature</em></p>
</xsl:if>
</div>
</xsl:template>
</xsl:stylesheet>
XSLT 2.0 and 3.0 Features
Grouping (XSLT 2.0+)
<xsl:for-each-group select="book" group-by="@category">
<h2><xsl:value-of select="current-grouping-key()"/> Books</h2>
<ul>
<xsl:for-each select="current-group()">
<li><xsl:value-of select="title"/></li>
</xsl:for-each>
</ul>
</xsl:for-each-group>
Regular Expressions (XSLT 2.0+)
<xsl:if test="matches(title, '^The\s+')">
<span>Starts with 'The'</span>
</xsl:if>
<xsl:value-of select="replace(description, '\s+', ' ')"/>
User-Defined Functions (XSLT 2.0+)
<xsl:function name="local:calculate-discount">
<xsl:param name="price" as="xs:decimal"/>
<xsl:param name="discount-rate" as="xs:decimal"/>
<xsl:value-of select="$price * (1 - $discount-rate)"/>
</xsl:function>
<!-- Usage -->
<xsl:value-of select="local:calculate-discount(price, 0.1)"/>
Performance Optimization
Use Keys for Lookups
<!-- Instead of: book[author = 'Harper Lee'] -->
<xsl:key name="books-by-author" match="book" use="author"/>
<xsl:value-of select="key('books-by-author', 'Harper Lee')/title"/>
Avoid Deep Recursion
<!-- Better: Use for-each instead of recursive templates -->
<xsl:for-each select="//item">
<!-- Process items -->
</xsl:for-each>
Minimize XPath Complexity
<!-- Cache complex expressions in variables -->
<xsl:variable name="expensiveBooks" select="book[price > 15]"/>
<xsl:value-of select="count($expensiveBooks)"/>
Debugging XSLT
Using xsl:message
<xsl:template match="book">
<xsl:message>
Processing book: <xsl:value-of select="title"/>
Price: <xsl:value-of select="price"/>
</xsl:message>
<!-- Continue processing -->
</xsl:template>
Common Debugging Techniques
- Start simple: Begin with basic templates and add complexity
- Use xsl:copy-of: Copy nodes to see structure
- Check XPath expressions: Test XPath separately
- Validate output: Ensure output format is correct
Best Practices
Template Design
- Specific patterns: Write specific match patterns
- Modular templates: Break complex logic into smaller templates
- Default templates: Provide fallback templates
- Mode usage: Use modes for different output formats
Code Organization
<!-- Group related templates -->
<!-- Book processing templates -->
<xsl:template match="book" mode="summary">
<!-- Summary format -->
</xsl:template>
<xsl:template match="book" mode="detailed">
<!-- Detailed format -->
</xsl:template>
<!-- Author processing templates -->
<xsl:template match="author">
<!-- Author format -->
</xsl:template>
Error Handling
Input Validation
<xsl:template match="book">
<xsl:choose>
<xsl:when test="not(title)">
<p class="error">Book missing title</p>
</xsl:when>
<xsl:when test="not(author)">
<p class="error">Book missing author</p>
</xsl:when>
<xsl:otherwise>
<!-- Normal processing -->
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Safe Number Conversion
<xsl:variable name="price" select="number(price)"/>
<xsl:if test="not($price = $price)"> <!-- NaN check -->
<span class="error">Invalid price</span>
</xsl:if>
Conclusion
XSLT is a powerful declarative language for XML transformation. Its template-based approach and XPath integration make it ideal for converting XML data into various output formats. While it has a learning curve, mastering XSLT provides excellent capabilities for XML data processing and document transformation.
Next Steps
- Learn XQuery for functional XML processing
- Explore XPath to improve your expressions
- Study XML Transformation for broader transformation techniques