We've now introduced a number of elements
into our XML structure that we're actually using elsewhere in different ways.
For example, we use the <Character> element to indicate the name of a character in a description, and
to hold information about a character within the <CastMember> element. The template makes no distinction between these two uses of the <Character> element, and does the same thing for each, making a <span> element with a character
class:
<xsl:template
match="Character">
<span class="character">
<xsl:apply-templates />
</span>
</xsl:template>
At the moment, we're also creating a <span> element when we create the cast list, in the template matching the <CastMember> element:
<xsl:template
match="CastMember">
<li>
<span class="character">
<xsl:apply-templates select="Character" />
</span>
<span class="actor">
<xsl:apply-templates select="Actor" />
</span>
</li>
</xsl:template>
This means we end up with two <span> elements around the names of the characters in the cast list. We
can get rid of the superfluous <span> element either by reverting back to using <xsl:value-of> to get the name of the character, or by removing the <span> element in the template that matches <CastMember> elements (and the same applies for the <Actor> elements as well, since we might name actors in a description):
<xsl:template
match="CastMember">
<li>
<xsl:apply-templates select="Character" />
<xsl:apply-templates select="Actor" />
</li>
</xsl:template>
So now we have
the same template being used to process <Character> elements in different contexts. However, one of
the extensions that we made earlier in this chapter was to have the <Character> and <Actor> elements within <CastMember> actually give both a name and a description of the
character. When we take this into account, we have a problem because we don't
want the <span> element to contain both the name and
the description of the character. We need a different template for the <Character> and <Actor> elements when they are children of <CastMember> elements (ones that just apply templates to the <Name> element child of the <Character> or <Actor> element). The new templates for the <Character> elements that occur in <CastMember> elements need to look like:
<xsl:template
match="Character">
<span class="character">
<xsl:apply-templates select="Name" />
</span>
</xsl:template>
But we can't use this template with the <Character> elements that occur in <Description> elements because they don't contain <Name> elements. If we use this template with those elements, the <span> elements won't have any content.
So we need some way of having different
templates for the different contexts in which these elements are allowed. We
can do this by changing the value of the match attribute of <xsl:template>, and this is where we need to use patterns, which are why they are
introduced next.
So far we've seen four kinds of values for
the match attribute of <xsl:template>:
/ matches the root
node
* matches any
element
text() matches text
nodes
the name of an element matches that
element
These values are all examples of patterns. An XSLT processor uses the pattern specified in the match attribute of <xsl:template> to work out whether it can use a template to process a node to which you've told it to
apply templates. In our case,
we need one pattern to match <Character> elements that are children of <CastMember> elements, and another pattern to match <Character> elements that appear at any level within <Description> elements. In both cases, we're checking the context in which the <Character> element appears. The two patterns we need are:
CastMember/Character
to match <Character>
elements that occur within <CastMember> elements
Description//Character to match <Character>
elements that occur nested to any level within <Description> elements
These types of patterns are technically
known as location path patterns.
As you can see, location path patterns look a lot like the location paths that
we use to select nodes to process in select
attributes, and it can be easy to get confused between the two. You use location paths to select nodes; they point from the
current node to a set of other nodes in the tree, stepping down from element to
child. You use location path patterns to match nodes; they test whether a
particular node has particular ancestors, looking up the node tree to work out
the context of the node.
A location path pattern is made up of a
number of step patterns,
separated by either / or //. If the separator is a /, then
the pattern tests a parent-child relationship. For example, the pattern Description/Character matches <Character>
elements whose immediate parent is a <Description> element. If the separator is //, on the other hand, then the pattern tests an ancestor-descendent
relationship. For example, the pattern Description//Character matches <Character>
elements that have a <Description>
element as an ancestor at any level.
Location
path patterns enable you to match elements according to the context in which
they occur.
In our XML document, TVGuide3.xml, there are some elements that have different meanings in different
contexts. If you remember back that far, it was one of our design decisions
when we first put together our XML structure that we would make use of the context an element was in, rather than use different
names for elements in different contexts, to work out what an element meant and
what we should do with it.
So now we have to deal with that
decision by creating different templates with different match patterns for the
different contexts in which an element can occur. The contexts within which different elements can occur are shown in the following table:
|
Element
|
Contexts
|
|
<Name>
|
child of <Channel>
child of <Character>
child of <Actor>
|
|
<Description>
|
child of <Program>
child of <Character>
child of <Actor>
|
|
<Character>
|
child of <CastMember>
descendent of <Description>
|
|
<Actor>
|
child of <CastMember>
descendent of <Description>
|
|
<Series>
|
child of <Program>
descendent of <Description>
|
|
<Program>
|
child of <Channel>
descendent of <Description>
|
|
<Channel>
|
child of <TVGuide>
descendent of <Description>
|
A stylesheet that deals with documents that
follow our markup language really needs to have templates that deal with
elements occurring in each of these possible contexts, using patterns that
include
ancestry information.
At this stage, we'll create a new version
of the stylesheet, TVGuide9.xsl, which
contains separate templates for each of these elements in each of these contexts. You should be able to put together different
templates for the elements in their different contexts as mapping rules. For
example, the <Name> element
can occur in three contexts – <Channel>, <Character>,
and <Actor> – so there should be
three corresponding templates:
<xsl:template
match="Channel/Name">...</xsl:template>
<xsl:template
match="Character/Name">...</xsl:template>
<xsl:template
match="Actor/Name">...</xsl:template>
As you add these templates, you should
consider whether some of the HTML that you're currently generating in
higher-level templates can be generated in lower-level templates instead. For
example, my feeling is that the <Name> element in the <Channel> element maps on to the <h2> heading element in the result, so the template should look like:
<xsl:template
match="Channel/Name">
<h2 class="channel"><xsl:value-of
select="." /></h2>
</xsl:template>
But if you create the <h2> element in the above template, you don't need to create it in the
template for the <Channel>
element. So you need to change that template too, removing the <h2> element that you were creating within it:
<xsl:template
match="TVGuide/Channel">
<xsl:apply-templates select="Name" />
<xsl:apply-templates
select="Program" />
</xsl:template>
This
template only applies to <Channel> elements that are children of the <TVGuide> element, not those that are descendents of <Description> elements.
If you go through this process religiously,
you should end up with about 19 different templates, as in TVGuide9.xsl. Using TVGuide9.xsl with TVGuide3.xml results in a page in which both the <Character> elements within the cast list and those within the <Description> are treated properly, so the result looks like the following when
you view it in Internet Explorer:
This process of adding templates on an
element-by-element basis, taking account of the different contexts in which an
element can occur, has left us with a lot of templates, but it has made the
stylesheet more robust. We've handled the problem that we had with <Character> elements being treated differently in different contexts, and we've
included templates to handle the possibility of things that aren't actually
present in TVGuide3.xml, but which
could happen in more complete documents that follow the markup language, such
as <Channel> elements in <Description> elements.
Adding templates to deal with every kind of
element, in every context, within a stylesheet can leave you with a stylesheet
that contains lots of templates that actually don't do very much. This makes
the stylesheet harder for you to maintain (because it's longer) and it makes
more work for the processor when it needs to identify which template to apply in a particular situation. You need to find a judicious
balance between the two.
First, you don't need to create a template
for every element in every context. Remember that if the XSLT processor can't
find a template that matches a node, then it will use a built-in template. If
an element (and all its content) gets processed by the built-in templates, then
you'll just get the value of the node. So, if you have templates that look like
either of the following:
<xsl:template
match="...">
<xsl:value-of select="." />
</xsl:template>
<xsl:template
match="...">
<xsl:apply-templates />
</xsl:template>
then you may as well get rid of them.
Second, you can get rid of templates that
essentially process the children of the element that they match in the order
that they appear. For example, the revised template for the <Channel> elements that are children of <TVGuide> elements is like that:
<xsl:template
match="TVGuide/Channel">
<xsl:apply-templates select="Name" />
<xsl:apply-templates select="Program" />
</xsl:template>
When a <Channel> element appears as a child of a <TVGuide> element then it can only contain a <Name> element followed by any number of <Program> elements. So telling the processor to apply templates first to the <Name> element and then to the <Program> elements has the same effect as telling the processor to apply
templates to all the <Channel>
element's children in the order they occur, which would be the template:
<xsl:template
match="TVGuide/Channel">
<xsl:apply-templates />
</xsl:template>
This template has the same effect as the
built-in template for elements, so you can delete it without affecting the
result of the stylesheet.
Finally, you can get rid of templates that
match elements that are never selected for processing. Remember that a template
is never actually used if the processor never gets told to apply templates to a
node that matches that template. A template that is never matched will never be
used, and can therefore be safely removed.
TVGuide9.xsl does
contain a few templates that aren't really necessary, and to make it easier to
understand we could prune it to
create TVGuide10.xsl.
There are three templates that do the same
thing as the built-in templates, and just contain an <xsl:apply-templates> or an <xsl:value-of>
instruction that gives the value of the current node. They are the one matching
<Name> element children of <Character> elements, the one matching <Name> element children of <Actor> elements, and the one matching <Description> element children of <Program> elements – you can safely delete them:
<xsl:template
match="Character/Name">
<xsl:value-of select="." />
</xsl:template>
<xsl:template
match="Actor/Name">
<xsl:value-of select="." />
</xsl:template>
<xsl:template
match="Program/Description">
<xsl:apply-templates />
</xsl:template>
We identified the template matching <Channel> element children of the <TVGuide> element as falling into the second category. It had two <xsl:apply-templates> in it, but these selected nodes in the same order as they occurred in the source
document, essentially the same as applying templates to all the children, which
again is just what the built-in templates do. So you can safely delete the
following template:
<xsl:template
match="TVGuide/Channel">
<xsl:apply-templates select="Name" />
<xsl:apply-templates select="Program" />
</xsl:template>
Finally, in TVGuide9.xsl there are two templates that can never be applied – the one
matching <Description>
elements within <Character>
elements, and the one matching <Description> elements within <Actor> elements. These templates will never get activated because the
templates that match <Character>
and <Actor> elements (within <CastMember> elements) never apply templates to their child <Description> elements. So these two templates can also be deleted:
<xsl:template
match="Character/Description" />
<xsl:template
match="Actor/Description" />
Once you've done all that, you should have
something like the following stylesheet, TVGuide10.xsl. The ordering of the templates within the stylesheet doesn't
matter.
<?xml
version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
...
</html>
</xsl:template>
<xsl:template
match="Channel/Name">
<h2 class="channel"><xsl:value-of
select="." /></h2>
</xsl:template>
<xsl:template
match="Channel/Program">
<div>
...
</div>
</xsl:template>
<xsl:template match="Program/Series">
<span class="title"><xsl:value-of
select="." /></span>
</xsl:template>
<xsl:template
match="CastMember">
<li>
<xsl:apply-templates select="Character" />
<xsl:apply-templates select="Actor" />
</li>
</xsl:template>
<xsl:template
match="CastMember/Character">
<span class="character">
<xsl:apply-templates select="Name" />
</span>
</xsl:template>
<xsl:template
match="CastMember/Actor">
<span class="actor">
<xsl:apply-templates select="Name" />
</span>
</xsl:template>
<xsl:template
match="Description//Character">
<span class="character">
<xsl:apply-templates />
</span>
</xsl:template>
<xsl:template
match="Description//Actor">
<span class="actor">
<xsl:apply-templates />
</span>
</xsl:template>
<xsl:template
match="Link">
<a href="{@href}">
<xsl:apply-templates />
</a>
</xsl:template>
<xsl:template
match="Description//Program">
<span class="program"><xsl:apply-templates
/></span>
</xsl:template>
<xsl:template
match="Description//Series">
<span class="series"><xsl:apply-templates
/></span>
</xsl:template>
<xsl:template
match="Description//Channel">
<span class="channel"><xsl:apply-templates
/></span>
</xsl:template>
</xsl:stylesheet>
Using TVGuide10.xsl with TVGuide3.xml should
give the same result as TVGuide9.xsl did.
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.
|
|