1. xml
  2. /advanced
  3. /xslt

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 &lt; 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 &lt; 12">
                <span class="price-low">Budget Pick</span>
            </xsl:when>
            <xsl:when test="price &gt; 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(. &lt; ../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 &gt; 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 &lt; 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

  1. Start simple: Begin with basic templates and add complexity
  2. Use xsl:copy-of: Copy nodes to see structure
  3. Check XPath expressions: Test XPath separately
  4. 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