We've seen how to use separate templates to
process different nodes in different ways. However, one thing that we might
lose when we use templates like this is the ability to process the same node in different ways in different
situations. For example, say we want to create a table of contents for the HTML
page that we're generating, giving a list of the channels that the TV guide offers, as shown in the following
screenshot:
The HTML underlying the above page is as
follows:
...
<h1>TV
Guide</h1>
<p>
[<a href="#BBC1">BBC1</a>]
[<a href="#BBC2">BBC2</a>]
[<a href="#ITV">ITV</a>]
[<a href="#Channel 4">Channel 4</a>]
[<a href="#Channel 5">Channel 5</a>]
</p>
<h2 class="channel"><a
name="BBC1" id="BBC1">BBC1</a></h2>
...
We can get the table of contents by
processing the <Channel>
elements, but we're already processing the <Channel> elements to get the lists of the programs available on each
channel. We want to process the same <Channel> elements twice:
once for their entry in the channel list and once to get their details.
This is fairly easy with <xsl:for-each> because the content of the particular <xsl:for-each> determines the result that you get. So we could do:
<h1>TV
Guide</h1>
<p>
<xsl:for-each select="TVGuide/Channel">
[<a href="#{Name}"><xsl:value-of
select="Name" /></a>]
</xsl:for-each>
</p>
<xsl:apply-templates
/>
to get the entries in the channel list, and
then either use another <xsl:for-each> or apply templates (as above) to the <Channel> elements to get their content later on.
However, you can also achieve this by using
template modes. Modes allow you
to process the same node with different templates in different situations. You can apply templates in a particular mode using the mode attribute on <xsl:apply-templates>, and you can define the mode for a template with the mode attribute on <xsl:template>.
When you apply templates in a particular mode then the XSLT processor will only
look at those templates with that mode.
With modes, then, you can apply templates
to the same node in different modes to get different results. So in this case,
we can use a template in ChannelList mode to generate the result for each channel in the channel list,
as follows:
<xsl:template
match="Channel" mode="ChannelList">
[<a
href="#{Name}"><xsl:value-of select="Name"
/></a>]
</xsl:template>
This template will only match <Channel> elements if you apply templates in ChannelList mode. So we need to have an <xsl:apply-templates> element in the template for the root node that applies templates in
ChannelList mode:
<h1>TV
Guide</h1>
<p>
<xsl:apply-templates select="TVGuide/Channel"
mode="ChannelList" />
</p>
<xsl:apply-templates
/>
When
you apply templates without setting the mode, then the processor uses templates
that don't have a mode attribute.
You can use moded templates to get different processing for the same
node in different situations.
As you'll recall, when an XSLT processor
can't find a template that matches a particular node then it will use a
built-in template instead. There are
similar built-in templates for moded templates, one for elements, which simply applies templates to their content in the same mode:
<xsl:template
match="*" mode="any-mode">
<xsl:apply-templates mode="any-mode" />
</xsl:template>
and another that matches text nodes in that
mode and gives their value:
<xsl:template
match="text()" mode="any-mode">
<xsl:value-of select="." />
</xsl:template>
These built-in templates mean that you can
get rid of superfluous templates and apply templates without explicitly
specifying the nodes to which you're applying them in exactly the same way as
you can with the default mode.
When applying templates in ChannelList mode, then, we don't have to specify the nodes to which we're
applying templates and can just use:
<h1>TV
Guide</h1>
<p>
<xsl:apply-templates mode="ChannelList" />
</p>
<xsl:apply-templates
/>
The current node in the template (which
matches the root node) is the root node, so the <xsl:apply-templates> in ChannelList mode
with no select attribute
will apply templates to the document element, the <TVGuide> element in ChannelList mode. There isn't a template for the <TVGuide> element in ChannelList mode, so the processor will apply the built-in moded template. This
template selects the children of the <TVGuide> element, the <Channel> elements, and applies templates to them in ChannelList mode.
There are built-in
moded templates in the same way as there are built-in normal templates.
There are three
things that we need to do to TVGuide16.xsl to create a linked list of channels in our page:
1.
Add anchors to the headings for
the channels in the main body of the page
2.
Create a template in ChannelList mode to give the link to each channel
3. Apply templates in ChannelList mode at
the point at which the channel list should be given on the page
We'll make these three changes to create a
new version of our stylesheet, TVGuide17.xsl.
You can add the
anchors to the headings by changing the template for the <Name> element child of the <Channel> element to include an <a> element whose name and id attributes give the name of
the channel:
<xsl:template match="Channel/Name">
<h2 class="channel">
<a name="{.}"
id="{.}"><xsl:value-of select="." /></a>
</h2>
</xsl:template>
The
XHTML Recommendation advises that you use both the name and id attributes when
creating anchors for backward and forward compatibility. We're not yet
generating proper XHTML, but it's a good guideline to follow, as that's our
final goal.
You can create the template for <Channel> elements in ChannelList mode to reference these links, as follows:
<xsl:template
match="Channel" mode="ChannelList">
[<a href="#{Name}"><xsl:value-of
select="Name" /></a>]
</xsl:template>
Finally, you can apply templates in ChannelList mode in the template matching the root node to insert the channel
list just under the title. The main content of the page comes after this
channel list and is generated by applying templates in the normal mode:
<xsl:template
match="/">
<html>
<head>
...
</head>
<body>
<h1>TV Guide</h1>
<p>
<xsl:apply-templates mode="ChannelList" />
</p>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
You could repeat the same list again at the
bottom of the page very easily, by repeating the same instruction after the <xsl:apply-templates> that generates the main body of the page:
<xsl:template
match="/">
<html>
<head>
...
</head>
<body>
<h1>TV Guide</h1>
<p>
<xsl:apply-templates mode="ChannelList" />
</p>
<xsl:apply-templates />
<p>
<xsl:apply-templates mode="ChannelList" />
</p>
</body>
</html>
</xsl:template>
This demonstrates one of the advantages of
using templates over using <xsl:for-each> – you can reuse the same code by applying the same template.
Transforming TVGuide3.xml with TVGuide17.xsl gives the display that we were aiming for, with a list of channel
names at the top and the bottom of the page. Clicking on the channel name takes
you to the program listing for that channel.
Buy Beginning XSLT here
© Copyright 2002 Wrox Press
This chapter is written by Jeni Tennison
and taken from "Beginning XSLT" published by Wrox Press Limited in June 2002; ISBN 1861005946; copyright © Wrox Press Limited 2002; all rights reserved.
No part of these chapters may be reproduced, stored in a retrieval system or transmitted in any form or by any means -- electronic, electrostatic, mechanical, photocopying, recording or otherwise -- without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.
|
|