For loops vs. apply-templates
I have recently started using XSLT for some of my XML documents and I have some questions. I add the code below. In the code I have a template that matches ebook elements. I then want to list all the authors that wrote the book. I do it using a for each loop, but I could also apply a template to it. I can't see a clear line when to use loops and when to use templates.
And another question is it normal to just say apply-templates when you now that there wont be other children of the element where you are writing it. In my case in the template that matches the document root I say apply-templates. Then it finds ebooks which is the only child of it, but I could have a "books" element which distinguish between "regular" books, and electronic books then it would just list the character data of the books. I then would have needed to write apply-templates select="ebooks" if I just wanted the ebooks in my final document. So is this a case of that it depends of how well you know your document?
Thank you, here is my code (This is just for practicing):
XML:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="ebooks.xsl"?>
<ebooks>
<ebook>
<title>Advanced Rails Recipes: 84 New Ways to Build Stunning Rails Apps</title>
<authors>
<author><name>Mike Clark</name></author>
</authors>
<pages>464</pages>
<isbn>978-0-9787-3922-5</isbn>
<programming_language>Ruby</programming_language>
<date>
<year>2008</year>
<month>5</month>
<day>1</day>
</date>
<publisher>The Pragmatic Programmers</publisher>
</ebook>
...
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<html>
<head>
<title>Library</title>
</head>
<body>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<xsl:template match="ebooks">
<h1>Ebooks</h1>
<xsl:apply-templates>
<xsl:sort select="title"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="ebook">
<h3><xsl:value-of select="title"/></h3>
<xsl:apply-templates select="date" />
<xsl:for-each select="authors/author/name">
<b><xsl:value-of select="."/>,</b>
</xsl:for-each>
</xsl:template>
<xsl:template match="date">
<table border="1">
<tr>
<th>Day</th>
<th>Month</th>
<th>Year</th>
</tr>
<tr>
<td><xsl:value-of select="day"/></td>
<td><xsl:value-of select="month"/></td>
<td><xsl:value-of select="year"/></td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>
This question is a bit argumentative, but here is my take on it.
I can't see a clear line when to use loops and when to use templates.
I'd say that you shoud strive to avoid for-each
as often as possible in favor of apply-templates
.
Using for-each
makes your program more complex by adding nesting levels and it's also impossibe to re-use the code inside the for-each
block. Using apply-templates
will (when done right) generate more more flexible and modular XSLT.
On the other hand: If you write a stylesheet with limited complexity and re-usability or modualarization are not a concern, using for-each
may be quicker and easier to follow (for a human reader/maintainer). So it's partly a question of personal preference.
In your case, I would find this elegant:
<xsl:template match="ebook">
<!-- ... -->
<xsl:apply-templates select="authors/author" />
<!-- ... -->
</xsl:template>
<xsl:template match="authors/author">
<b>
<xsl:value-of select="name"/>
<xsl:if test="position() < last()">,</xsl:if>
</b>
</xsl:template>
To your other question
And another question is it normal to just say
apply-templates
when you know that there won't be other children of the element where you are writing it.
When you know there will never be any children in this element, writing apply-templates
is pointless.
When done right, XSLT has the ability to flexibly deal with varying input. If you expect there could be children at some point, an apply-templates
won't hurt.
A simple apply-templates
without select
is rather unspecific, both in terms of which (i.e.: all of them) and in what order (i.e.: input document order) nodes will be processed. So you could end up either processing nodes you never wanted to process or node you already have processed earlier.
Since one cannot write a sensible template for unknown nodes, I tend to avoid the unspecific apply-templates
and just adapt my stylesheet when the input changes.
I do it using a for each loop, but I could also apply a template to it. I can't see a clear line when to use loops and when to use templates.
Using <xsl:for-each>
is in no way harmful if one knows exactly how an <xsl:for-each>
is processed.
The trouble is that a lot of newcomers to XSLT that have experience in imperative programming take <xsl:for-each>
as a substitute of a "loop" in their favorite PL and think that it allows them to perform the impossible -- like incrementing a counter or any other modification of an already defined <xsl:variable>
.
One indispensable use of <xsl:for-each>
in XSLT 1.0 is to change the current document -- this is often needed in order to be able to use the key()
function on a document, different from the current source XML document, for example to efficiently access lookup-table that resides in its own xml document.
On the other side, using <xsl:template>
and <xsl:apply-templates>
is much more powerful and elegant.
Here are some of the most important differences between the two approaches:
xsl:apply-templates
is much richer and deeper thanxsl:for-each
, even simply because we don't know what code will be applied on the nodes of the selection -- in the general case this code will be different for different nodes of the node-list.The code that will be applied can be written way after the
xsl:apply template
s was written and by people that do not know the original author.
The FXSL library's implementation of higher-order functions (HOF) in XSLT wouldn't be possible if XSLT didn't have the <xsl:apply-templates>
instruction.
another question is it normal to just say apply-templates when you (k)now that there wont be other children of the element where you are writing it
<xsl:apply-templates/>
is a shorthand for:
<xsl:apply-templates select="child::node()"/>
Even if there are other children of the current node, about which you dont't care, you coud still use the short <xsl:apply-templates>
and have another template like that:
<xsl:template match="*"/>
This template ignores ("deletes") any element. You should override it with more specific templates (in XSLT, generally, more specific templates have higher priority and get selected for processing over less-specific templates matching the same node):
<xsl:template match="ebook">
<!-- Necessary processing here -->
</xsl:template>
I usually don't use <xsl:template match="*"/>
but I use another template that matches (and ignores) every text node:
<xsl:template match="text()"/>
This has typically the same effect as using <xsl:template match="*"/>
, because of the way XSLT processes nodes for which there is no matching template. In any such case XSLT uses its built-in templates in what may be called "default processing.
The result of XSLT's default processing of a subtree rooted in an element is to output the concatenation (in document order) of all text-node descendents of this element.
Therefore, preventing the output of any text node using <xsl:template match="text()"/>
has the same (null) output result as preventing output from processing an element by using <xsl:template match="*"/>
.
Summary:
Templates and the
<xsl:apply-templates>
instruction is how XSLT implements and deals with polymorphism.It is in the spirit of XSLT's processing model to use more generic templates matching large classes of nodes and doing some default processing for them and later overriding the generic templates with more specific ones that match and process only nodes we are interested in.
Reference: See this whole thread: http://www.stylusstudio.com/xsllist/200411/post60540.html
Generally I agree with Dimitre's answer. The main reason for advising beginners to use xsl:apply-templates rather than xsl:for-each is that once they're comfortable and experienced with xsl:apply-templates, they won't find it difficult to decide between the two constructs, whereas until they acquire that experience, they will use xsl:for-each inappropriately.
The main benefits of xsl:apply-templates over for-each are that the code adapts better to changing document structures and changing processing requirements. It's difficult to sell that benefit to people who just want to hack some code together and don't care what the world will be like in three years' time, but it's a very real benefit.