Give parent elements a class when children is active with XSLT
Until recently XSLT was completely new to me, and I've been working on a menu/submenu in XSLT for a little while with a danish CMS called Dynamicweb.
I don't know if this is a Dynamicweb specific question or a XSLT related question, but I'll ask anyway.
My current XSLT document looks like this:
<xsl:template match="//Page">
<xsl:param name="depth"/>
<li>
<xsl:attribute name="id">
<xsl:value-of select="concat('', translate(translate(@MenuText, translate(@MenuText, $validRange, ''), ''), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'))" />
</xsl:attribute>
<a>
<xsl:attribute name="class">
<!-- Add .inpath class -->
<xsl:if test="@InPath='True'">
<xsl:text> inpath</xsl:text>
</xsl:if>
<!-- Add .firstitem class -->
<xsl:if test="position() = 1">
<xsl:text> firstitem</xsl:text>
</xsl:if>
<!-- Add .miditem class -->
<xsl:if test="position() > 1 and position() < count(//Page)">
<xsl:text> miditem</xsl:text>
</xsl:if>
<!-- Add .lastitem class -->
<xsl:if test="position() = count(//Page)">
<xsl:text> lastitem</xsl:text>
</xsl:if>
<!-- Add .active class -->
<xsl:if test="@Active = 'True'">
<xsl:text> active</xsl:text>
</xsl:if>
</xsl:attribute>
<!-- Add link ID (URL friendly menu text) -->
<xsl:attribute name="id">
<xsl:value-of select="concat('anchor-', translate(translate(@MenuText, translate(@MenuText, $validRange, ''), ''), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'))" />
</xsl:attribute>
<!-- Add link URL -->
<xsl:attribute name="href">
<xsl:value-of select="@FriendlyHref" disable-output-escaping="yes" />
</xsl:attribute>
<!-- Add link text -->
<span><xsl:value-of select="@MenuText" disable-output-escaping="yes" /></span>
</a>
<xsl:if test="count(Page) and @MenuText != 'Home'">
<ul class="level{@AbsoluteLevel+1}">
<xsl:apply-templates select="Page">
<xsl:with-param name="depth" select="$depth+1"/>
</xsl:apply-templates>
</ul>
</xsl:if>
</li>
</xsl:template>
You can see how I add classes based on Dynamicweb tags for active
and inpath
. The thing is that further down in the document (not pasted here) is a code for printing out submenu (ul
and li
). If I click a submenu item, that item gets the active
class, but is there a way to give the parent the active
class too?
Update: Added the raw XML (sorry for the messy XML).
<?xml v开发者_如何学编程ersion="1.0" encoding="utf-8"?>
<NavigationTree>
<Settings>
<Pageview ID="1" AreaID="1" MenuText="Home" Title="Home" NavigationName="" />
<Setting Level="1">
<NavigationImage Value="" />
<NavigationMouseoverImage Value="" />
<NavigationActiveImage Value="" />
<NavigationImgAfter Value="" />
<NavigationDividerImage Value="" />
<NavigationHideSpacer Value="True" />
<NavigationSpace Value="0" />
</Setting>
<Setting Level="2">
<NavigationImage Value="" />
<NavigationMouseoverImage Value="" />
<NavigationActiveImage Value="" />
<NavigationImgAfter Value="" />
<NavigationDividerImage Value="" />
<NavigationHideSpacer Value="" />
<NavigationSpace Value="0" />
</Setting>
<Setting Level="3">
<NavigationImage Value="" />
<NavigationMouseoverImage Value="" />
<NavigationActiveImage Value="" />
<NavigationImgAfter Value="" />
<NavigationDividerImage Value="" />
<NavigationHideSpacer Value="" />
<NavigationSpace Value="0" />
</Setting>
<Setting Level="4">
<NavigationImage Value="" />
<NavigationMouseoverImage Value="" />
<NavigationActiveImage Value="" />
<NavigationImgAfter Value="" />
<NavigationDividerImage Value="" />
<NavigationHideSpacer Value="" />
<NavigationSpace Value="3" />
</Setting>
<Setting Level="5">
<NavigationImage Value="" />
<NavigationMouseoverImage Value="" />
<NavigationActiveImage Value="" />
<NavigationImgAfter Value="" />
<NavigationDividerImage Value="" />
<NavigationHideSpacer Value="" />
<NavigationSpace Value="3" />
</Setting>
</Settings>
<Page ID="1" AreaID="1" MenuText="Home" MouseOver="" Href="Default.aspx?ID=1" FriendlyHref="/default/home.aspx" Image="" ImageActive="" ImageMouseOver="" Title="" Allowclick="True" ShowInSitemap="True" AbsoluteLevel="1" RelativeLevel="1" Sort="1" LastInLevel="False" InPath="True" ChildCount="3" class="L1_Active" Active="True" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True">
<Page ID="6" AreaID="1" MenuText="News" MouseOver="" Href="Default.aspx?ID=6" FriendlyHref="/default/home/news.aspx" Image="" ImageActive="" ImageMouseOver="" Title="" Allowclick="True" ShowInSitemap="True" AbsoluteLevel="2" RelativeLevel="2" Sort="1" LastInLevel="False" InPath="False" ChildCount="0" class="L2" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True" />
<Page ID="7" AreaID="1" MenuText="About" MouseOver="" Href="Default.aspx?ID=7" FriendlyHref="/default/home/about.aspx" Image="" ImageActive="" ImageMouseOver="" Title="" Allowclick="True" ShowInSitemap="True" AbsoluteLevel="2" RelativeLevel="2" Sort="2" LastInLevel="False" InPath="False" ChildCount="0" class="L2" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True" />
<Page ID="8" AreaID="1" MenuText="Presence" MouseOver="" Href="Default.aspx?ID=8" FriendlyHref="/default/home/presence.aspx" Image="" ImageActive="" ImageMouseOver="" Title="" Allowclick="True" ShowInSitemap="True" AbsoluteLevel="2" RelativeLevel="2" Sort="3" LastInLevel="True" InPath="False" ChildCount="0" class="L2" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True" />
</Page>
<Page ID="2" AreaID="1" MenuText="Hygiene" MouseOver="" Href="Default.aspx?ID=14" FriendlyHref="/default/hygiene.aspx" Image="" ImageActive="" ImageMouseOver="" Title="" Allowclick="False" ShowInSitemap="True" AbsoluteLevel="1" RelativeLevel="1" Sort="2" LastInLevel="False" InPath="False" ChildCount="2" class="L1" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True">
<Page ID="14" AreaID="1" MenuText="Professional" MouseOver="" Href="Default.aspx?ID=14" FriendlyHref="/default/hygiene/professional.aspx" Image="/Files/Navigation/menu_antibac_01.gif" ImageActive="/Files/Navigation/menu_antibac_02.gif" ImageMouseOver="/Files/Navigation/menu_antibac_02.gif" Title="" Allowclick="True" ShowInSitemap="True" AbsoluteLevel="2" RelativeLevel="2" Sort="1" LastInLevel="False" InPath="False" ChildCount="0" class="L2" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True" />
<Page ID="15" AreaID="1" MenuText="Private" MouseOver="" Href="Default.aspx?ID=15" FriendlyHref="/default/hygiene/private.aspx" Image="/Files/Navigation/menu_antibac_01.gif" ImageActive="/Files/Navigation/menu_antibac_02.gif" ImageMouseOver="/Files/Navigation/menu_antibac_02.gif" Title="" Allowclick="True" ShowInSitemap="True" AbsoluteLevel="2" RelativeLevel="2" Sort="2" LastInLevel="True" InPath="False" ChildCount="0" class="L2" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True" />
</Page>
<Page ID="3" AreaID="1" MenuText="Household & Leisure" MouseOver="" Href="Default.aspx?ID=3" FriendlyHref="/default/household---leisure.aspx" Image="" ImageActive="" ImageMouseOver="" Title="" Allowclick="True" ShowInSitemap="True" AbsoluteLevel="1" RelativeLevel="1" Sort="3" LastInLevel="False" InPath="False" ChildCount="0" class="L1" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True" />
<Page ID="4" AreaID="1" MenuText="Car Care" MouseOver="" Href="Default.aspx?ID=4" FriendlyHref="/default/car-care.aspx" Image="" ImageActive="" ImageMouseOver="" Title="" Allowclick="True" ShowInSitemap="True" AbsoluteLevel="1" RelativeLevel="1" Sort="4" LastInLevel="False" InPath="False" ChildCount="1" class="L1" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True">
<Page ID="20" AreaID="1" MenuText="Carix" MouseOver="" Href="Default.aspx?ID=20" FriendlyHref="/default/car-care/carix.aspx" Image="/Files/Navigation/menu_carix_01.gif" ImageActive="/Files/Navigation/menu_carix_02.gif" ImageMouseOver="/Files/Navigation/menu_carix_02.gif" Title="" Allowclick="True" ShowInSitemap="True" AbsoluteLevel="2" RelativeLevel="2" Sort="1" LastInLevel="True" InPath="False" ChildCount="0" class="L2" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True" />
</Page>
<Page ID="5" AreaID="1" MenuText="Industrial Chemicals" MouseOver="" Href="Default.aspx?ID=5" FriendlyHref="/default/industrial-chemicals.aspx" Image="" ImageActive="" ImageMouseOver="" Title="" Allowclick="True" ShowInSitemap="True" AbsoluteLevel="1" RelativeLevel="1" Sort="5" LastInLevel="True" InPath="False" ChildCount="0" class="L1" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True" />
</NavigationTree>
Well, a couple of observations:
You don't need the leading slashes in
match='//Page'
. The XPath in thematch
attribute is used to select a template, not navigate to a node.You don't need a
depth
parameter; since the depth of aPage
is the number ofPage
ancestors it has, you can just usecount(ancestor::Page)
to calculate the depth.If you want to get the
Active
attribute by walking up the tree ofPage
elements, useancestor-or-self::Page[last()]/@Active
. The ancestor-or-self axis is a list of nodes that starts with the context node and includes its parent, its parent's parent, and so on, until the TLE is reached. ThePage
part finds onlyPage
elements on that axis, and the[last()]
predicate finds the last of thosePage
elements, which will always be the highest-levelPage
element.I strongly suspect that your use of
count(//Page)
is not going to serve you well in the long run. That counts every singlePage
element in the source tree, irrespective of where it is found. Is that really what you want? Or do you want to know where the currentPage
element is relative to its siblingPage
elements? If so, you can just doposition() != 1 and position() != last()
. This works becauseposition()
returns the context node's position relative to its expression context. That is, whenxsl:apply-templates select='*'
is invoked, it creates a set of elements and finds a matching template for each. That's the expression context; the third element in that list will have aposition()
of 3.There's no need to do
test='count(Page)
.test='Page'
does the same thing; it evaluates to true if there's any childPage
element. It's both more readable and very likely faster.
Edit
Incorporated Tomalak's observation (see comments).
Edit
You know, I should actually read these questions. In your context, to give a parent element a class when it or one of its children is active, the opposite of what I've done, you'd do:
<xsl:if test="@Active='True' or Page[@Active = 'True']">
<xsl:text> active</xsl:text>
</xsl:if>
But Tomalak's example, which uses the descendant-or-self
axis, is more likely what you want to use, if the idea is to activate a menu if anything anywhere within it is active. You could also do this:
<xsl:if test="@Active='True' or .//Page[@Active = 'True']">
<xsl:text> active</xsl:text>
</xsl:if>
as ".//" is really just a shortcut for descendant::
.
I've created a solution to your problem (and I have heavily modified your approach in the process):
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
<xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="validRange" select="concat($upper, $lower)" />
<xsl:template match="NavigationTree">
<ul class="level1">
<xsl:apply-templates select="Page">
<xsl:sort select="@Sort" data-type="number" />
</xsl:apply-templates>
</ul>
</xsl:template>
<xsl:template match="Page">
<xsl:variable name="idText" select="
translate(
translate(@MenuText, translate(@MenuText, $validRange, ''), ''), $upper, $lower
)
" />
<li id="{$idText}">
<a href="{@FriendlyHref}" id="anchor-{$idText}">
<xsl:attribute name="class">
<xsl:if test="position() = 1">firstitem </xsl:if>
<xsl:if test="position() > 1 and position() < last()">miditem </xsl:if>
<xsl:if test="position() = last()">lastitem </xsl:if>
<xsl:if test="@InPath='True'">inpath </xsl:if>
<!-- the descendant-or-self XPath axis solves your question! -->
<xsl:if test="descendant-or-self::Page[@Active='True']">active </xsl:if>
</xsl:attribute>
<span>
<xsl:value-of select="@MenuText" />
</span>
</a>
<xsl:if test="Page">
<ul class="level{Page/@AbsoluteLevel}">
<xsl:apply-templates select="Page">
<xsl:sort select="@Sort" data-type="number" />
</xsl:apply-templates>
</ul>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
Which results in (when page 20 is set to "active"):
<ul class="level1">
<li id="home">
<a href="/default/home.aspx" id="anchor-home" class="firstitem inpath">
<span>Home</span>
</a>
<ul class="level2">
<li id="news">
<a href="/default/home/news.aspx" id="anchor-news" class="firstitem">
<span>News</span>
</a>
</li>
<li id="about">
<a href="/default/home/about.aspx" id="anchor-about" class="miditem">
<span>About</span>
</a>
</li>
<li id="presence">
<a href="/default/home/presence.aspx" id="anchor-presence" class="lastitem">
<span>Presence</span>
</a>
</li>
</ul>
</li>
<li id="hygiene">
<a href="/default/hygiene.aspx" id="anchor-hygiene" class="miditem">
<span>Hygiene</span>
</a>
<ul class="level2">
<li id="professional">
<a href="/default/hygiene/professional.aspx" id="anchor-professional" class="firstitem">
<span>Professional</span>
</a>
</li>
<li id="private">
<a href="/default/hygiene/private.aspx" id="anchor-private" class="lastitem">
<span>Private</span>
</a>
</li>
</ul>
</li>
<li id="householdleisure">
<a href="/default/household---leisure.aspx" id="anchor-householdleisure" class="miditem">
<span>Household & Leisure</span>
</a>
</li>
<li id="carcare">
<a href="/default/car-care.aspx" id="anchor-carcare" class="miditem active">
<span>Car Care</span>
</a>
<ul class="level2">
<li id="carix">
<a href="/default/car-care/carix.aspx" id="anchor-carix" class="firstitem lastitem active">
<span>Carix</span>
</a>
</li>
</ul>
</li>
<li id="industrialchemicals">
<a href="/default/industrial-chemicals.aspx" id="anchor-industrialchemicals" class="lastitem">
<span>Industrial Chemicals</span>
</a>
</li>
</ul>
Be aware that you do not want disable-output-escaping="yes"
, I've removed that since it would produce malformed XML.
精彩评论