Whenever you have rules in a language, such
as rules in CSS or templates in XSLT, you need some way to resolve conflicts
when two of the rules apply to the same situation. What would happen, for
example, if you had one template that matched
all <Character> elements and another
template that matched only those <Character> elements that had <CastMember> as their parent:
<xsl:template
match="Character">
<span class="character"><xsl:apply-templates
/></span>
</xsl:template>
<xsl:template
match="CastMember/Character">
<span class="character"><xsl:apply-templates
select="Name" /></span>
</xsl:template>
A <Character> element in a <Description> element only matches one of the templates, because it doesn't have
a <CastMember> element as its
parent, so obviously the XSLT processor uses that template with it. But what
about a <Character>
element in a <CastMember>
element? It matches both of the template's patterns, so what should the XSLT
processor do?
Well, the XSLT processor will only ever
process one template when you apply templates to a node, so it has to choose
between the two templates that it's presented with in some way. It does this by
looking at the template's priority. A
template with a high priority is chosen over a template with a lower
priority.
You can specifically assign a template a
priority using the priority attribute
on the <xsl:template>
element. The priority attribute
can be set to any number, including decimal and negative numbers. For example,
you can give the two templates different specific priorities, as
follows:
<xsl:template
match="Character" priority="-1">
<span
class="character"><xsl:apply-templates /></span>
</xsl:template>
<xsl:template
match="CastMember/Character" priority="2">
<span
class="character"><xsl:apply-templates select="Name"
/></span>
</xsl:template>
However, it would be very difficult to
assign and keep track of priorities if the priority attribute was your only option. If you don't specify a priority attribute on a template, the XSLT processor assigns it a priority
based on how specific its match pattern is. XSLT processors recognize three levels of
priority in patterns:
Patterns that match a class of nodes,
such as *, which matches all
elements, are assigned an implicit priority of -0.5
Patterns that match nodes according to
their name, such as Character, which
matches <Character>
elements, are assigned an implicit priority of 0
Patterns that match nodes according
to their context, such as CastMember/Character, which matches <Character> elements whose parent is a <CastMember> element, are assigned an implicit priority of 0.5
When
assigning priorities based on patterns, it doesn't matter how specific the
context information is: if you specify any context for a node then the template
has a priority of 0.5. For example, Description/Link/Character has exactly the same priority as Description//Character.
Technically, it's an error if you have two
templates that match the same node and the match patterns for the two templates
have the same specificity. However, most processors recover from the error and
use the template that you've defined last in the stylesheet. You should try to
avoid having templates that have the same priority and can feasibly match the
same node; use the priority attribute
to assign them specific, different priorities.
If two templates match the same
node, the processor uses the one with the highest priority. Priority can be
assigned explicitly with the priority
attribute or determined implicitly from the template's match pattern. As a last
resort, the processor can select the last matching template in the stylesheet.
Currently, the templates that our
stylesheet contains don't have any conflicts with each other because each of
them only matches elements in a fairly specific context. To try out priorities,
let's try making some of the templates conflict by removing some of that
context information from one of them. For example, in TVGuide11.xsl, let's change
the template that matches <Program> elements within <Description> elements to match any <Program> element:
<xsl:template
match="Program">
<span class="program"><xsl:apply-templates
/></span>
</xsl:template>
Now a <Program> element will always be matched by this template, and if it's a
child of a <Channel>
element then it will also match the following template:
<xsl:template
match="Channel/Program">
<div>
<p>
<span class="date"><xsl:apply-templates
select="Start" /></span>
<br />
<xsl:apply-templates select="Series" />
<br />
<xsl:apply-templates select="Description" />
<span onclick="toggle({Series}Cast);">[Cast]</span>
</p>
<div id="{Series}Cast" style="display:
none;">
<ul class="castlist">
<xsl:apply-templates
select="CastList/CastMember" />
</ul>
</div>
</div>
</xsl:template>
However, if you run TVGuide11.xsl with TVGuide3.xml, it
won't make any difference to the result because the latter template, matching <Program> element children of <Channel> elements, has a higher priority. When the <Program> element that the XSLT processor is trying to process is a child of
a <Channel> element, it will use
the latter template, with the match pattern of Channel/Program; when it's not (such as when it's a child of a <Description> element), the processor will use the former template, with the
match pattern of Program.
But we can change that in two ways. First,
we can assign different priorities to the two templates using the priority attribute. Assign the general template a priority of 1, to create TVGuide12.xsl:
<xsl:template
match="Program" priority="1">
<span
class="program"><xsl:apply-templates /></span>
</xsl:template>
This explicit priority is higher than the
implicit priority of the second template. If you run TVGuide12.xsl with TVGuide3.xml, you
get the following mess:
Some of the formatting is still present
because the elements inside the <Program> element still get processed by this template. However, the parts of
the result that were generated by the second template, such as the <div> and <p> elements,
are no longer present. You can get the same effect by removing the priority on
the template matching all <Program> elements and giving the template matching <Program> elements within <Channel> elements a priority lower than -0.5.
A second way of manipulating the priority
of the templates is to remove the context from the path for the second template
(and remove the priority attributes
that you just added), so the template that's supposed to be used to process <Program> elements within <Channel> elements looks like:
<xsl:template
match="Program">
<div>
<p>
<span
class="date"><xsl:apply-templates select="Start"
/></span>
<br />
<xsl:apply-templates
select="Series" />
<br />
<xsl:apply-templates
select="Description" />
<span
onclick="toggle({Series}Cast);">[Cast]</span>
</p>
<div
id="{Series}Cast" style="display: none;">
<ul
class="castlist">
<xsl:apply-templates select="CastList/CastMember" />
</ul>
</div>
</div>
</xsl:template>
Now the templates both apply to the same <Program> elements and have the same priority, which is an error. Move the
template that's supposed to be for <Program> elements within <Description> elements below the above in the stylesheet (if it's not there
already), to give TVGuide13.xsl. Most
likely your processor will use the lower template in the stylesheet to process
the <Program> elements, and you'll
get the same mess as when you changed the priority explicitly. Processors are
within their rights to terminate the stylesheet and give you an error, though,
and some processors might warn you that there are two templates that match the
same node with the same priority. Saxon, for example, gives reams of
recoverable error messages, though it produces the result perfectly well:
It's often
simpler to make templates whose match patterns don't include any information
about the ancestry of the element, and it's easier for the processor too,
because it doesn't have to check. For our TV guide stylesheet, we could adopt
one of two styles to deal with elements that need to be treated differently in
different places – add ancestry information for those templates that deal with
elements in the descriptions, or add ancestry information to the other
templates, those that deal with the bulk of the result. I think it makes more
sense to keep ancestry information on the templates that deal with
descriptions, because that way it's easy to tell which templates are those that
deal with descriptions and which aren't. TVGuide14.xsl shows the result of doing that.
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.
|
|