Advanced XSLT: Transforming XML to HTML, PDF, and More
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
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 completeCommon XSLT Errors
- Missing namespace declaration — XSLT elements use the
http://www.w3.org/1999/XSL/Transformnamespace. Missingxmlns:xsl="..."causes elements to be treated as regular XML. - xsl:for-each vs apply-templates confusion —
xsl:for-eachiterates within the current context;apply-templateslets the XSLT processor choose matching templates. Useapply-templatesfor polymorphic behavior. - Output method mismatch — Setting
method="html"but outputting XML tags (like self-closing<div/>) causes rendering issues. Match the output method to your content. - XSLT 1.0 functions only — Using XSLT 2.0 functions (like
current-grouping-key()orformat-number()with advanced patterns) requiresversion="2.0"orversion="3.0"on the stylesheet element. - 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.
- XPath context in for-each — Inside
xsl:for-each, the context changes. Use../orcurrent()to reference outer elements. A common mistake is using a path that worked outside the loop. - Template priority conflicts — When multiple templates match the same node, XSLT uses priority to decide. Use
priority="1"on more specific templates andpriority="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
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