开发者

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() &gt; 1 and position() &lt; 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 &amp; 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:

  1. You don't need the leading slashes in match='//Page'. The XPath in the match attribute is used to select a template, not navigate to a node.

  2. You don't need a depth parameter; since the depth of a Page is the number of Page ancestors it has, you can just use count(ancestor::Page) to calculate the depth.

  3. If you want to get the Active attribute by walking up the tree of Page elements, use ancestor-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. The Page part finds only Page elements on that axis, and the [last()] predicate finds the last of those Page elements, which will always be the highest-level Page element.

  4. I strongly suspect that your use of count(//Page) is not going to serve you well in the long run. That counts every single Page 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 current Page element is relative to its sibling Page elements? If so, you can just do position() != 1 and position() != last(). This works because position() returns the context node's position relative to its expression context. That is, when xsl: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 a position() of 3.

  5. There's no need to do test='count(Page). test='Page' does the same thing; it evaluates to true if there's any child Page 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() &gt; 1 and position() &lt; 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 &amp; 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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜