Skip to content
Advanced XSLT: Transforming XML to HTML, PDF, and More

Advanced XSLT: Transforming XML to HTML, PDF, and More

DodaTech Updated Jun 20, 2026 7 min read

Advanced XSLT extends beyond basic XML-to-HTML transformation into grouping, sorting, multiple outputs, and even PDF generation via XSL-FO. XSLT 2.0 and 3.0 add powerful features like xsl:for-each-group, schema awareness, and higher-order functions.

Learning Path

    flowchart LR
  A["XSLT Basics<br/>Templates & Match"] --> B["Advanced XSLT<br/>Grouping & Sorting"]
  B --> C["XPath Functions<br/>Reference"]
  C --> D["SOAP APIs<br/>XML Web Services"]
  style B fill:#f90,color:#fff,stroke-width:2px
  
What you’ll learn: XSLT 2.0/3.0 grouping (xsl:for-each-group), advanced template matching, sorting, number formatting, multiple output documents, and XSL-FO for PDF. Why it matters: XSLT transforms the world’s enterprise data — from bank statements to insurance documents to product catalogs. Advanced techniques handle real-world messy data. Real-world use: DodaZIP uses XSLT to transform XML compression reports into formatted HTML and PDF for client delivery. Durga Antivirus Pro transforms threat intelligence XML into dashboard view data.

XSLT Version Features

    flowchart TD
    subgraph "XSLT 1.0 (1999)"
        A1["Basic templates<br/>xsl:value-of<br/>xsl:for-each"]
    end
    subgraph "XSLT 2.0 (2007)"
        B1["xsl:for-each-group<br/>Schema awareness<br/>Multiple output files<br/>Regex functions"]
    end
    subgraph "XSLT 3.0 (2017)"
        C1["Streaming<br/>Higher-order functions<br/>xsl:try/xsl:catch<br/>xsl:merge"]
    end
    A1 --> B1 --> C1
  

Source XML: Book Catalog

<?xml version="1.0" encoding="UTF-8"?>
<catalog>
    <book category="fiction" pubyear="1937">
        <title>The Hobbit</title>
        <author>J.R.R. Tolkien</author>
        <price currency="USD">12.99</price>
        <rating>4.8</rating>
    </book>
    <book category="fiction" pubyear="1954">
        <title>The Fellowship of the Ring</title>
        <author>J.R.R. Tolkien</author>
        <price currency="USD">14.99</price>
        <rating>4.9</rating>
    </book>
    <book category="non-fiction" pubyear="1988">
        <title>A Brief History of Time</title>
        <author>Stephen Hawking</author>
        <price currency="GBP">9.99</price>
        <rating>4.5</rating>
    </book>
    <book category="fiction" pubyear="1960">
        <title>To Kill a Mockingbird</title>
        <author>Harper Lee</author>
        <price currency="USD">11.99</price>
        <rating>4.7</rating>
    </book>
    <book category="science" pubyear="2010">
        <title>The Selfish Gene</title>
        <author>Richard Dawkins</author>
        <price currency="GBP">8.99</price>
        <rating>4.4</rating>
    </book>
</catalog>

Grouping with xsl:for-each-group

XSLT 2.0’s xsl:for-each-group groups elements by any value — the most requested feature missing from XSLT 1.0.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="html" indent="yes"/>

    <xsl:template match="/">
        <html>
        <head><title>Books Grouped by Author</title></head>
        <body>
            <h1>Book Catalog</h1>
            <xsl:for-each-group select="catalog/book" group-by="author">
                <h2><xsl:value-of select="current-grouping-key()"/></h2>
                <ul>
                    <xsl:for-each select="current-group()">
                        <li>
                            <xsl:value-of select="title"/>
                            (<xsl:value-of select="price"/>)
                        </li>
                    </xsl:for-each>
                </ul>
            </xsl:for-each-group>
        </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

Expected HTML output:

<html>
<head><title>Books Grouped by Author</title></head>
<body>
    <h1>Book Catalog</h1>
    <h2>J.R.R. Tolkien</h2>
    <ul>
        <li>The Hobbit ($12.99)</li>
        <li>The Fellowship of the Ring ($14.99)</li>
    </ul>
    <h2>Stephen Hawking</h2>
    <ul>
        <li>A Brief History of Time (£9.99)</li>
    </ul>
    <h2>Harper Lee</h2>
    <ul>
        <li>To Kill a Mockingbird ($11.99)</li>
    </ul>
    <h2>Richard Dawkins</h2>
    <ul>
        <li>The Selfish Gene (£8.99)</li>
    </ul>
</body>
</html>

Advanced Template Matching

XSLT 2.0/3.0 supports mode-based and priority-based templates:

<xsl:stylesheet version="3.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="html" indent="yes"/>

    <!-- Default template: simply copy -->
    <xsl:template match="text()" priority="0"/>

    <!-- Title in "summary" mode -->
    <xsl:template match="title" mode="summary">
        <strong><xsl:value-of select="."/></strong>
    </xsl:template>

    <!-- Title in "detail" mode -->
    <xsl:template match="title" mode="detail">
        <h3><xsl:value-of select="."/></h3>
        <p>Published: <xsl:value-of select="../@pubyear"/></p>
        <p>Rating: <xsl:value-of select="../rating"/></p>
    </xsl:template>

    <!-- Price formatting with currency symbol -->
    <xsl:template match="price">
        <xsl:choose>
            <xsl:when test="@currency = 'USD'">$</xsl:when>
            <xsl:when test="@currency = 'GBP'">£</xsl:when>
            <xsl:when test="@currency = 'EUR'"></xsl:when>
            <xsl:otherwise><xsl:value-of select="@currency"/> </xsl:otherwise>
        </xsl:choose>
        <xsl:value-of select="."/>
    </xsl:template>

    <!-- Apply templates in different modes -->
    <xsl:template match="/">
        <html>
        <body>
            <h1>Summary</h1>
            <xsl:apply-templates select="catalog/book/title" mode="summary"/>
            <h1>Details</h1>
            <xsl:apply-templates select="catalog/book" mode="detail"/>
        </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

Sorting with Multiple Keys

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="/">
        <catalog-sorted>
            <xsl:for-each select="catalog/book">
                <!-- Sort by category, then by rating descending -->
                <xsl:sort select="@category" order="ascending"/>
                <xsl:sort select="rating" order="descending" data-type="number"/>
                <xsl:copy-of select="."/>
            </xsl:for-each>
        </catalog-sorted>
    </xsl:template>
</xsl:stylesheet>

Expected output:

<catalog-sorted>
    <book category="fiction" pubyear="1954"><title>Fellowship...</title><rating>4.9</rating></book>
    <book category="fiction" pubyear="1937"><title>The Hobbit</title><rating>4.8</rating></book>
    <book category="fiction" pubyear="1960"><title>To Kill a Mockingbird</title><rating>4.7</rating></book>
    <book category="non-fiction" pubyear="1988"><title>A Brief History...</title><rating>4.5</rating></book>
    <book category="science" pubyear="2010"><title>The Selfish Gene</title><rating>4.4</rating></book>
</catalog-sorted>

Number Formatting

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>

    <xsl:template match="/">
        <xsl:for-each select="catalog/book">
Title: <xsl:value-of select="title"/>
Price: <xsl:value-of select="format-number(price, '#,##0.00')"/>
ISBN:  <xsl:value-of select="format-number(position(), '000')"/>
---
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

Expected output:

Title: The Hobbit
Price: 12.99
ISBN: 001
---
Title: The Fellowship of the Ring
Price: 14.99
ISBN: 002
---

Multiple Output Documents

XSLT 2.0 can write to multiple files using xsl:result-document:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xsl:output method="html" indent="yes"/>

    <xsl:template match="/">
        <!-- Generate index page -->
        <xsl:result-document href="index.html" method="html">
            <html>
            <head><title>Book Catalog</title></head>
            <body>
                <h1>Book Catalog</h1>
                <ul>
                    <xsl:for-each select="catalog/book">
                        <li>
                            <a href="{generate-id()}.html">
                                <xsl:value-of select="title"/>
                            </a>
                        </li>
                    </xsl:for-each>
                </ul>
            </body>
            </html>
        </xsl:result-document>

        <!-- Generate individual book pages -->
        <xsl:for-each select="catalog/book">
            <xsl:result-document href="{generate-id()}.html" method="html">
                <html>
                <head><title><xsl:value-of select="title"/></title></head>
                <body>
                    <h1><xsl:value-of select="title"/></h1>
                    <p>Author: <xsl:value-of select="author"/></p>
                    <p>Price: <xsl:value-of select="price"/></p>
                    <p>Category: <xsl:value-of select="@category"/></p>
                    <a href="index.html">Back to catalog</a>
                </body>
                </html>
            </xsl:result-document>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

This generates index.html and one HTML file per book.

XSL-FO for PDF Generation

XSL-FO (Formatting Objects) turns XML into PDF through a pipeline: XML → XSLT → XSL-FO → PDF (using Apache FOP).

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fo="http://www.w3.org/1999/XSL/Format">

    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="/">
        <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
            <fo:layout-master-set>
                <fo:simple-page-master master-name="page"
                    page-width="8.5in" page-height="11in">
                    <fo:region-body margin="1in"/>
                </fo:simple-page-master>
            </fo:layout-master-set>

            <fo:page-sequence master-reference="page">
                <fo:flow flow-name="xsl-region-body">
                    <fo:block font-size="24pt" font-weight="bold"
                              text-align="center" space-after="12pt">
                        Book Catalog
                    </fo:block>

                    <xsl:for-each select="catalog/book">
                        <fo:block font-size="14pt" font-weight="bold"
                                  space-before="12pt">
                            <xsl:value-of select="title"/>
                        </fo:block>
                        <fo:block font-size="10pt" space-after="6pt">
                            Author: <xsl:value-of select="author"/>
                        </fo:block>
                        <fo:block font-size="10pt">
                            Price: <xsl:value-of select="price"/>
                            <xsl:text> </xsl:text>
                            <xsl:value-of select="@currency"/>
                        </fo:block>
                    </xsl:for-each>
                </fo:flow>
            </fo:page-sequence>
        </fo:root>
    </xsl:template>
</xsl:stylesheet>

Python XSLT Processing

from lxml import etree

def xslt_transform(xml_file, xslt_file, output_file=None):
    """Transform XML using XSLT."""
    xml_doc = etree.parse(xml_file)
    xslt_doc = etree.parse(xslt_file)
    transform = etree.XSLT(xslt_doc)
    result = transform(xml_doc)
    
    if output_file:
        with open(output_file, 'wb') as f:
            f.write(result)
        print(f"Wrote {len(result)} bytes to {output_file}")
    else:
        print(str(result)[:500])

# Test
xslt_transform("catalog.xml", "group-by-author.xsl", "catalog.html")
print("Transformation complete")

Expected output:

Wrote 1024 bytes to catalog.html
Transformation complete

Common XSLT Errors

  1. Missing namespace declaration — XSLT elements use the http://www.w3.org/1999/XSL/Transform namespace. Missing xmlns:xsl="..." causes elements to be treated as regular XML.
  2. xsl:for-each vs apply-templates confusionxsl:for-each iterates within the current context; apply-templates lets the XSLT processor choose matching templates. Use apply-templates for polymorphic behavior.
  3. Output method mismatch — Setting method="html" but outputting XML tags (like self-closing <div/>) causes rendering issues. Match the output method to your content.
  4. XSLT 1.0 functions only — Using XSLT 2.0 functions (like current-grouping-key() or format-number() with advanced patterns) requires version="2.0" or version="3.0" on the stylesheet element.
  5. Relative href in xsl:result-document — Output file paths are relative to the stylesheet’s base URI, not the source XML. Use absolute paths or ensure correct working directory.
  6. XPath context in for-each — Inside xsl:for-each, the context changes. Use ../ or current() to reference outer elements. A common mistake is using a path that worked outside the loop.
  7. Template priority conflicts — When multiple templates match the same node, XSLT uses priority to decide. Use priority="1" on more specific templates and priority="0" (or omit) on defaults.

Practice Questions

1. What is the difference between xsl:for-each and xsl:apply-templates? xsl:for-each explicitly iterates over a node set. xsl:apply-templates lets the XSLT processor choose which template to apply to each node, enabling polymorphic template matching.

2. How does xsl:for-each-group work? It groups nodes by a key (from group-by attribute). Inside the group, current-grouping-key() returns the group key and current-group() returns the group’s nodes.

3. What is XSL-FO used for? XSL-FO (Formatting Objects) is an XML vocabulary for describing page layouts. Combined with a renderer like Apache FOP, it transforms XML into PDF documents.

4. How do you write to multiple output files in XSLT 2.0? Use xsl:result-document with an href attribute specifying the output file path. Each xsl:result-document generates a separate output file.

5. Challenge: Build a report generator Create an XSLT 2.0 stylesheet that: groups books by category, sorts by rating descending, formats prices with currency symbols, and generates both HTML and PDF (XSL-FO) output.

Mini Project: Automated XML Report Generator

import subprocess
import os

def generate_report():
    """Generate multiple output formats from XML catalog."""
    report_dir = "reports"
    os.makedirs(report_dir, exist_ok=True)
    
    steps = [
        ("Generate HTML", "catalog.xml", "to-html.xsl", 
         f"{report_dir}/catalog.html"),
        ("Generate XSL-FO", "catalog.xml", "to-fo.xsl",
         f"{report_dir}/catalog.fo"),
    ]
    
    for step_name, xml, xslt, output in steps:
        subprocess.run([
            "xsltproc", "-o", output, xslt, xml
        ])
        size = os.path.getsize(output)
        print(f"{step_name}: {output} ({size} bytes)")
    
    # Convert FO to PDF using Apache FOP
    if subprocess.run(["which", "fop"], capture_output=True).returncode == 0:
        subprocess.run([
            "fop", "-xml", f"{report_dir}/catalog.fo",
            "-pdf", f"{report_dir}/catalog.pdf"
        ])
        print(f"PDF: {report_dir}/catalog.pdf")

generate_report()

FAQ

What’s the difference between XSLT 1.0 and 2.0?
XSLT 2.0 added grouping (xsl:for-each-group), schema awareness, multiple output documents (xsl:result-document), regular expression functions, and better date/time handling. XSLT 1.0 is still widely used but missing these essential features.
Can XSLT generate JSON or CSV output?
Yes. Set method="text" and use xsl:value-of to output comma-separated values or JSON-formatted text. XSLT 3.0 has built-in JSON parsing with fn:json-to-xml().
Is XSLT still relevant in 2026?
For enterprise data transformation, yes. Millions of XML-based systems (banking, insurance, publishing, healthcare) rely on XSLT. JSON has replaced XML for web APIs, but XSLT remains irreplaceable for document transformation workflows.
How do I debug XSLT?
Use <xsl:message> for debugging output. Many IDEs (Oxygen, VS Code with XML plugin) support step-through XSLT debugging. For complex issues, simplify to a minimal input example.
What’s the performance of XSLT 2.0/3.0 for large files?
XSLT 3.0 streaming processes files without loading them entirely into memory, handling gigabytes of XML. For non-streaming, XSLT processors like Saxon can process millions of nodes per second.

Related Tutorials


Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro