XSLT for Transformation
While XSLT is covered in detail in the Advanced section, this page focuses specifically on practical transformation applications and techniques for converting XML data between different formats and structures.
Common Transformation Scenarios
XML to HTML Conversion
Transform XML catalog data into a browsable HTML page:
<!-- Input XML: catalog.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<products>
<product id="1" category="electronics">
<name>Laptop</name>
<price currency="USD">999.99</price>
<description>High-performance laptop</description>
<specs>
<processor>Intel i7</processor>
<memory>16GB</memory>
<storage>512GB SSD</storage>
</specs>
</product>
<product id="2" category="books">
<name>XML Guide</name>
<price currency="USD">29.99</price>
<description>Complete XML reference</description>
</product>
</products>
</catalog>
<!-- XSLT Transformation: catalog-to-html.xsl -->
<?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"/>
<xsl:template match="/">
<html>
<head>
<title>Product Catalog</title>
<style>
.product { border: 1px solid #ccc; margin: 10px; padding: 15px; border-radius: 5px; }
.electronics { background-color: #e6f3ff; }
.books { background-color: #fff0e6; }
.price { font-size: 1.2em; font-weight: bold; color: #007700; }
.specs { background-color: #f5f5f5; padding: 10px; margin-top: 10px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<h1>Product Catalog</h1>
<!-- Summary Section -->
<div class="summary">
<h2>Catalog Summary</h2>
<p>Total Products: <xsl:value-of select="count(//product)"/></p>
<p>Categories: <xsl:value-of select="count(distinct-values(//product/@category))"/></p>
<p>Average Price: $<xsl:value-of select="format-number(sum(//product/price) div count(//product), '#.00')"/></p>
</div>
<!-- Products by Category -->
<xsl:for-each select="//product/@category[not(. = preceding::product/@category)]">
<xsl:variable name="category" select="."/>
<h2><xsl:value-of select="$category"/> Products</h2>
<xsl:for-each select="//product[@category = $category]">
<xsl:sort select="price" data-type="number"/>
<xsl:apply-templates select="." mode="product-card"/>
</xsl:for-each>
</xsl:for-each>
<!-- Product Comparison Table -->
<h2>Product Comparison</h2>
<table>
<tr>
<th>Name</th>
<th>Category</th>
<th>Price</th>
<th>Description</th>
</tr>
<xsl:for-each select="//product">
<xsl:sort select="@category"/>
<xsl:sort select="price" data-type="number"/>
<tr>
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="@category"/></td>
<td><xsl:value-of select="price/@currency"/> <xsl:value-of select="price"/></td>
<td><xsl:value-of select="description"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="product" mode="product-card">
<div class="product {translate(@category, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')}">
<h3><xsl:value-of select="name"/></h3>
<div class="price">
<xsl:value-of select="price/@currency"/> <xsl:value-of select="price"/>
</div>
<p><xsl:value-of select="description"/></p>
<xsl:if test="specs">
<div class="specs">
<h4>Specifications:</h4>
<ul>
<xsl:for-each select="specs/*">
<li><strong><xsl:value-of select="local-name()"/>:</strong> <xsl:value-of select="."/></li>
</xsl:for-each>
</ul>
</div>
</xsl:if>
</div>
</xsl:template>
</xsl:stylesheet>
XML to CSV Conversion
<!-- XSLT for CSV output: products-to-csv.xsl -->
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/">
<!-- CSV Header -->
<xsl:text>ID,Name,Category,Price,Currency,Description</xsl:text>
<xsl:text> </xsl:text>
<!-- CSV Data -->
<xsl:for-each select="//product">
<xsl:sort select="@id" data-type="number"/>
<xsl:value-of select="@id"/><xsl:text>,</xsl:text>
<xsl:text>"</xsl:text><xsl:value-of select="normalize-space(name)"/><xsl:text>",</xsl:text>
<xsl:value-of select="@category"/><xsl:text>,</xsl:text>
<xsl:value-of select="price"/><xsl:text>,</xsl:text>
<xsl:value-of select="price/@currency"/><xsl:text>,</xsl:text>
<xsl:text>"</xsl:text><xsl:value-of select="normalize-space(description)"/><xsl:text>"</xsl:text>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Data Restructuring
Grouping and Aggregation
Transform flat product data into grouped categories:
<!-- Transform to grouped structure -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<catalog-by-category>
<xsl:variable name="categories" select="//product/@category[not(. = preceding::product/@category)]"/>
<xsl:for-each select="$categories">
<xsl:variable name="current-category" select="."/>
<category name="{$current-category}">
<xsl:variable name="category-products" select="//product[@category = $current-category]"/>
<summary>
<product-count><xsl:value-of select="count($category-products)"/></product-count>
<total-value><xsl:value-of select="sum($category-products/price)"/></total-value>
<average-price><xsl:value-of select="sum($category-products/price) div count($category-products)"/></average-price>
</summary>
<products>
<xsl:for-each select="$category-products">
<xsl:sort select="price" data-type="number" order="descending"/>
<product>
<xsl:copy-of select="@*"/>
<xsl:copy-of select="*"/>
</product>
</xsl:for-each>
</products>
</category>
</xsl:for-each>
</catalog-by-category>
</xsl:template>
</xsl:stylesheet>
Flattening Hierarchical Data
Convert nested XML to flat structure:
<!-- Input: Nested order data -->
<orders>
<order id="1001" date="2023-07-01">
<customer>
<name>John Doe</name>
<email>[email protected]</email>
</customer>
<items>
<item id="A1" quantity="2" price="25.00">Widget A</item>
<item id="A2" quantity="1" price="15.00">Widget B</item>
</items>
</order>
</orders>
<!-- XSLT to flatten structure -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<flat-orders>
<xsl:for-each select="//item">
<order-item>
<order-id><xsl:value-of select="ancestor::order/@id"/></order-id>
<order-date><xsl:value-of select="ancestor::order/@date"/></order-date>
<customer-name><xsl:value-of select="ancestor::order/customer/name"/></customer-name>
<customer-email><xsl:value-of select="ancestor::order/customer/email"/></customer-email>
<item-id><xsl:value-of select="@id"/></item-id>
<item-name><xsl:value-of select="."/></item-name>
<quantity><xsl:value-of select="@quantity"/></quantity>
<unit-price><xsl:value-of select="@price"/></unit-price>
<total-price><xsl:value-of select="@quantity * @price"/></total-price>
</order-item>
</xsl:for-each>
</flat-orders>
</xsl:template>
</xsl:stylesheet>
Format Conversion
XML to JSON-like Structure
While XSLT can't directly output JSON, you can create a JSON-like text format:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/">
<xsl:text>{</xsl:text>
<xsl:text> "catalog": {</xsl:text>
<xsl:text> "products": [</xsl:text>
<xsl:for-each select="//product">
<xsl:text> {</xsl:text>
<xsl:text> "id": "</xsl:text><xsl:value-of select="@id"/><xsl:text>",</xsl:text>
<xsl:text> "category": "</xsl:text><xsl:value-of select="@category"/><xsl:text>",</xsl:text>
<xsl:text> "name": "</xsl:text><xsl:value-of select="name"/><xsl:text>",</xsl:text>
<xsl:text> "price": {</xsl:text>
<xsl:text> "amount": </xsl:text><xsl:value-of select="price"/><xsl:text>,</xsl:text>
<xsl:text> "currency": "</xsl:text><xsl:value-of select="price/@currency"/><xsl:text>"</xsl:text>
<xsl:text> },</xsl:text>
<xsl:text> "description": "</xsl:text><xsl:value-of select="description"/><xsl:text>"</xsl:text>
<xsl:if test="specs">
<xsl:text>,</xsl:text>
<xsl:text> "specs": {</xsl:text>
<xsl:for-each select="specs/*">
<xsl:text> "</xsl:text><xsl:value-of select="local-name()"/><xsl:text>": "</xsl:text><xsl:value-of select="."/><xsl:text>"</xsl:text>
<xsl:if test="position() != last()"><xsl:text>,</xsl:text></xsl:if>
</xsl:for-each>
<xsl:text> }</xsl:text>
</xsl:if>
<xsl:text> }</xsl:text>
<xsl:if test="position() != last()"><xsl:text>,</xsl:text></xsl:if>
</xsl:for-each>
<xsl:text> ]</xsl:text>
<xsl:text> }</xsl:text>
<xsl:text> }</xsl:text>
</xsl:template>
</xsl:stylesheet>
Advanced Transformation Techniques
Multi-Pass Transformations
<!-- First pass: Enrich data with calculations -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<enriched-catalog>
<xsl:apply-templates select="//product"/>
</enriched-catalog>
</xsl:template>
<xsl:template match="product">
<product>
<xsl:copy-of select="@*"/>
<xsl:copy-of select="*"/>
<!-- Add calculated fields -->
<calculated-fields>
<price-tier>
<xsl:choose>
<xsl:when test="price < 20">budget</xsl:when>
<xsl:when test="price < 100">mid-range</xsl:when>
<xsl:otherwise>premium</xsl:otherwise>
</xsl:choose>
</price-tier>
<relative-price>
<xsl:variable name="avg-price" select="sum(//product/price) div count(//product)"/>
<xsl:choose>
<xsl:when test="price > $avg-price * 1.5">above-average</xsl:when>
<xsl:when test="price < $avg-price * 0.5">below-average</xsl:when>
<xsl:otherwise>average</xsl:otherwise>
</xsl:choose>
</relative-price>
</calculated-fields>
</product>
</xsl:template>
</xsl:stylesheet>
Conditional Transformations
<!-- Transform based on context and conditions -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="output-format" select="'full'"/>
<xsl:param name="target-audience" select="'general'"/>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="$output-format = 'summary'">
<xsl:call-template name="summary-format"/>
</xsl:when>
<xsl:when test="$output-format = 'detailed'">
<xsl:call-template name="detailed-format"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="full-format"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="summary-format">
<product-summary>
<xsl:for-each select="//product">
<item>
<xsl:value-of select="name"/> - <xsl:value-of select="price/@currency"/><xsl:value-of select="price"/>
</item>
</xsl:for-each>
</product-summary>
</xsl:template>
<xsl:template name="detailed-format">
<detailed-catalog>
<xsl:for-each select="//product">
<product>
<xsl:copy-of select="@*"/>
<xsl:copy-of select="*"/>
<xsl:if test="$target-audience = 'technical'">
<technical-info>
<xml-structure>
<element-count><xsl:value-of select="count(.//*) "/></element-count>
<attribute-count><xsl:value-of select="count(.//@*)"/></attribute-count>
</xml-structure>
</technical-info>
</xsl:if>
</product>
</xsl:for-each>
</detailed-catalog>
</xsl:template>
</xsl:stylesheet>
Performance Tips for Transformations
Efficient XSLT Patterns
<!-- Use keys for efficient lookups -->
<xsl:key name="products-by-category" match="product" use="@category"/>
<xsl:template match="/">
<xsl:for-each select="//product/@category[not(. = preceding::product/@category)]">
<category name="{.}">
<xsl:for-each select="key('products-by-category', .)">
<xsl:copy-of select="."/>
</xsl:for-each>
</category>
</xsl:for-each>
</xsl:template>
<!-- Cache expensive calculations -->
<xsl:variable name="total-products" select="count(//product)"/>
<xsl:variable name="average-price" select="sum(//product/price) div $total-products"/>
<!-- Use efficient sorting -->
<xsl:for-each select="//product">
<xsl:sort select="price" data-type="number" order="descending"/>
<!-- Process sorted products -->
</xsl:for-each>
Transformation Utilities
String Manipulation Templates
<!-- Utility templates for common transformations -->
<xsl:template name="string-replace">
<xsl:param name="text"/>
<xsl:param name="search"/>
<xsl:param name="replace"/>
<xsl:choose>
<xsl:when test="contains($text, $search)">
<xsl:value-of select="substring-before($text, $search)"/>
<xsl:value-of select="$replace"/>
<xsl:call-template name="string-replace">
<xsl:with-param name="text" select="substring-after($text, $search)"/>
<xsl:with-param name="search" select="$search"/>
<xsl:with-param name="replace" select="$replace"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="titlecase">
<xsl:param name="text"/>
<xsl:value-of select="translate(substring($text, 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:value-of select="translate(substring($text, 2), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')"/>
</xsl:template>
Running XSLT Transformations
Command Line
# Using Saxon
java -jar saxon.jar -s:input.xml -xsl:transform.xsl -o:output.html
# Using xsltproc
xsltproc transform.xsl input.xml > output.html
# With parameters
xsltproc --param output-format "'summary'" transform.xsl input.xml
Java Integration
public class XSLTTransformer {
public void transform(String xmlPath, String xslPath, String outputPath)
throws TransformerException {
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(new StreamSource(xslPath));
// Set parameters if needed
transformer.setParameter("output-format", "detailed");
transformer.setParameter("target-audience", "technical");
transformer.transform(
new StreamSource(xmlPath),
new StreamResult(outputPath)
);
}
}
Conclusion
XSLT provides powerful capabilities for XML transformation, from simple format conversions to complex data restructuring. The key is understanding your transformation requirements and choosing the appropriate techniques for efficient and maintainable transformations.
Next Steps
- Explore XQuery for Transformation for functional approaches
- Learn Advanced Techniques for complex scenarios
- Study Tools and Processors for implementation options