XML Tutorial · XPath, XSLT and XQuery · Lesson 14 of 28
XSLT Basics
Transforming XML with XSLT 1.0: templates, xsl:value-of, xsl:for-each, xsl:if, xsl:sort, and running a stylesheet from code.
What XSLT does
XSLT turns one XML document into another document: different XML, HTML or plain text. A stylesheet is a set of templates: "when you meet a node matching this pattern, output this".
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/shop">
<xsl:for-each select="product[stock > 0]">
<xsl:sort select="price" data-type="number" order="descending"/>
<xsl:value-of select="name"/>: <xsl:value-of select="format-number(price, '0.00')"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
| Instruction | Does |
|---|---|
xsl:template match="…" |
a rule for matching nodes |
xsl:apply-templates select="…" |
process these nodes with their matching templates |
xsl:value-of select="…" |
output the string value of an XPath |
xsl:for-each / xsl:sort |
loop, optionally sorted |
xsl:if test="…" / xsl:choose |
conditions |
xsl:attribute, xsl:element, {…} in attributes |
build output nodes |
Running a stylesheet
XSLT 1.0 is built into lxml (libxslt), the JDK (javax.xml.transform) and browsers (XSLTProcessor, see
XML in JavaScript). PHP needs the xsl extension, which the in-browser PHP here
doesn't include.
from lxml import etree
shop = etree.fromstring(b"""<shop>
<product sku="TEA-01" category="drinks"><name>Green tea</name><price>3.50</price><stock>40</stock></product>
<product sku="COF-02" category="drinks"><name>Coffee beans</name><price>9.90</price><stock>0</stock></product>
<product sku="MUG-07" category="kitchen"><name>Camel mug</name><price>12.00</price><stock>15</stock></product>
<product sku="PAN-03" category="kitchen"><name>Frying pan</name><price>29.00</price><stock>4</stock></product>
</shop>""")
xslt = etree.XSLT(etree.fromstring(b"""
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/shop">
<xsl:for-each select="product[stock > 0]">
<xsl:sort select="price" data-type="number" order="descending"/>
<xsl:value-of select="name"/>: <xsl:value-of select="format-number(price, '0.00')"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>"""))
print(str(xslt(shop)), end="")
Output
Frying pan: 29.00 Camel mug: 12.00 Green tea: 3.50
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
String xml = """
<shop>
<product sku="TEA-01" category="drinks"><name>Green tea</name><price>3.50</price><stock>40</stock></product>
<product sku="COF-02" category="drinks"><name>Coffee beans</name><price>9.90</price><stock>0</stock></product>
<product sku="MUG-07" category="kitchen"><name>Camel mug</name><price>12.00</price><stock>15</stock></product>
<product sku="PAN-03" category="kitchen"><name>Frying pan</name><price>29.00</price><stock>4</stock></product>
</shop>""";
String xsl = """
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/shop">
<xsl:for-each select="product[stock > 0]">
<xsl:sort select="price" data-type="number" order="descending"/>
<xsl:value-of select="name"/>: <xsl:value-of select="format-number(price, '0.00')"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>""";
Transformer t = TransformerFactory.newInstance().newTransformer(new StreamSource(new StringReader(xsl)));
StringWriter out = new StringWriter();
t.transform(new StreamSource(new StringReader(xml)), new StreamResult(out));
System.out.print(out);
}
}
Output
Frying pan: 29.00 Camel mug: 12.00 Green tea: 3.50
Run this on your computer — see “Get Started” for setup.
Whitespace-only text in a stylesheet is ignored, so <xsl:text> is how you output exact spaces and new lines.
XML to HTML
Templates and xsl:apply-templates are the idiomatic style: each template handles one kind of node.
from lxml import etree
shop = etree.fromstring(b"""<shop>
<product sku="TEA-01" category="drinks"><name>Green tea</name><price>3.50</price><stock>40</stock></product>
<product sku="COF-02" category="drinks"><name>Coffee beans</name><price>9.90</price><stock>0</stock></product>
<product sku="MUG-07" category="kitchen"><name>Camel mug</name><price>12.00</price><stock>15</stock></product>
<product sku="PAN-03" category="kitchen"><name>Frying pan</name><price>29.00</price><stock>4</stock></product>
</shop>""")
to_html = etree.XSLT(etree.fromstring(b"""
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/shop">
<table>
<tr><th>Product</th><th>Price</th></tr>
<xsl:apply-templates select="product"/>
</table>
</xsl:template>
<xsl:template match="product">
<tr class="{@category}">
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="price"/></td>
</tr>
</xsl:template>
<xsl:template match="product[stock = 0]">
<tr class="sold-out"><td colspan="2"><xsl:value-of select="name"/> (sold out)</td></tr>
</xsl:template>
</xsl:stylesheet>"""))
print(str(to_html(shop)))
Output
<table> <tr> <th>Product</th> <th>Price</th> </tr> <tr class="drinks"> <td>Green tea</td> <td>3.50</td> </tr> <tr class="sold-out"><td colspan="2">Coffee beans (sold out)</td></tr> <tr class="kitchen"> <td>Camel mug</td> <td>12.00</td> </tr> <tr class="kitchen"> <td>Frying pan</td> <td>29.00</td> </tr> </table>
Test yourself
Which XSLT instruction processes the selected nodes with their matching templates?
xsl:apply-templatesxsl:value-ofxsl:copy-ofxsl:call-template
Show answer
Answer: xsl:apply-templates
apply-templates hands each selected node to the best-matching template.