views:

389

answers:

3

I'm developing a skin for DotNetNuke 5 using the DNN Done Right menu component which uses an custom XSL-T to convert the XML sitemap into an HTML navigation.

Using XSL-T is a life saver in comparison to the other ways of building a navigation menu. However I've run into problems doing something a little more complex, as I'm a newcomer to XML-T and XPath.

What I'm trying to achieve is this:

  • Ignore level 0 nodes
  • Loop through all level 1 nodes in the selected hierarchy
  • Once the selected level 1 node is found, start looping through the hierarchy/breadcrumb structure up to the active level.
  • Once at the active node level, loop through and show all the siblings of that level
  • Then finish off looping through the level 1 nodes

Below is a example XML block of a sitemap the menu component uses.

<Root>
    <root>
     <node id="37" text="Home" url="http://www.dnndoneright.com/Home.T37.aspx" enabled="1" selected="0" breadcrumb="0" first="1" last="0" only="0" depth="0" >
      <description >Upgrade your standard DNN menu - automatically and for free - to an SEO-optimised, accessible, mobile-friendly, cross-browser menu.</description>
     </node>

     <node id="56" text="DNN Menu" url="http://www.dnndoneright.com/DNN-Menu.T56.aspx" enabled="1" selected="0" breadcrumb="1" first="0" last="0" only="0" depth="0" >
      <node id="97" text="Menu features" url="http://www.dnndoneright.com/Menu-features.T97.aspx" enabled="1" selected="0" breadcrumb="0" first="1" last="0" only="0" depth="1" />
      <node id="111" text="Pre-built templates" url="http://www.dnndoneright.com/Pre-built-templates.T111.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="0" only="0" depth="1" >
       <node id="94" text="Standard DNN menu, upgraded" url="http://www.dnndoneright.com/Standard-DNN-menu-upgraded.T94.aspx" enabled="1" selected="0" breadcrumb="0" first="1" last="0" only="0" depth="2" />
       <node id="124" text="Superfish template" url="http://www.dnndoneright.com/Superfish-template.T124.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="0" only="0" depth="2" />
       <node id="106" text="Treeview template" url="http://www.dnndoneright.com/Treeview-template.T106.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="0" only="0" depth="2" />
       <node id="107" text="Accordion template" url="http://www.dnndoneright.com/Accordion-template.T107.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="0" only="0" depth="2" />
       <node id="112" text="Dropdown template" url="http://www.dnndoneright.com/Dropdown-template.T112.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="0" only="0" depth="2" />
       <node id="96" text="Mega-menu" url="http://www.dnndoneright.com/Mega-menu.T96.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="1" only="0" depth="2" />
      </node>
      <node id="113" text="Getting started" url="http://www.dnndoneright.com/Getting-started.T113.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="0" only="0" depth="1" >
       <node id="114" text="Installing the menu" url="http://www.dnndoneright.com/Installing-the-menu.T114.aspx" enabled="1" selected="0" breadcrumb="0" first="1" last="0" only="0" depth="2" />
       <node id="115" text="Upgrading a SolPart menu" url="http://www.dnndoneright.com/Upgrading-a-SolPart-menu.T115.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="0" only="0" depth="2" />
       <node id="116" text="Upgrading a DNNMenu" url="http://www.dnndoneright.com/Upgrading-a-DNNMenu.T116.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="0" only="0" depth="2" />
       <node id="117" text="Installing a template" url="http://www.dnndoneright.com/Installing-a-template.T117.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="0" only="0" depth="2" />
       <node id="118" text="Adding to a skin" url="http://www.dnndoneright.com/Adding-to-a-skin.T118.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="1" only="0" depth="2" />
      </node>
      <node id="119" text="Customising templates" url="http://www.dnndoneright.com/Customising-templates.T119.aspx"   enabled="0" selected="0" breadcrumb="1" first="0" last="1" only="0" depth="1" >
       <node id="120" text="Standard options" url="http://www.dnndoneright.com/Standard-options.T120.aspx"         enabled="1" selected="0" breadcrumb="0" first="1" last="0" only="0" depth="2" />
       <node id="121" text="Specifying filenames" url="http://www.dnndoneright.com/Specifying-filenames.T121.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="0" only="0" depth="2" />
       <node id="122" text="XML format" url="http://www.dnndoneright.com/XML-format.T122.aspx"                     enabled="1" selected="1" breadcrumb="1" first="0" last="0" only="0" depth="2" />
       <node id="123" text="Creating custom templates" url="http://www.dnndoneright.com/Creating-custom-templates.T123.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="1" only="0" depth="2" />
      </node>
     </node>

     <node id="87" text="Download" url="http://www.dnndoneright.com/Download.T87.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="0" only="0" depth="0" />

     <node id="85" text="Contact" url="http://www.dnndoneright.com/Contact.T85.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="1" only="0" depth="0" >
      <node id="129" text="Blog" url="http://www.dnndoneright.com/Blog.T129.aspx" enabled="1" selected="0" breadcrumb="0" first="1" last="0" only="0" depth="1" />
      <node id="130" text="Forum" url="http://www.dnndoneright.com/Forum.T130.aspx" enabled="1" selected="0" breadcrumb="0" first="0" last="1" only="0" depth="1" />
     </node>
    </root>
</Root>

Also here is my (incorrect) starting point, just to expose my lack of XSL/XPath knowledge. (My code is wrong, because it lists all L1 nodes, rather than just the L1 nodes in the selected hierarchy)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output method="html" />

    <xsl:template match="/">
    <div class="SideMenu">
      <!-- Loop through all L1 nodes -->
      <xsl:for-each select="Root/root/node/node">
        <xsl:if test="@enabled='1'" >
          <!-- Only use L1 in the current hierarchy -->

          <div>
            [ <xsl:value-of select="@text"/>: <xsl:value-of select="@depth"/> ]
          </div>
        </xsl:if>
      </xsl:for-each>
    </div>
    </xsl:template>

</xsl:stylesheet>
+1  A: 

The following assumes that there can only be one selected node in the input:

<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:template match="/">
    <div class="SideMenu">
      <!-- select all the siblings of the selected node -->
      <xsl:apply-templates select="//node[@selected=1]/../node" />
    </div>
  </xsl:template>

  <!-- nodes are turned into a <div> with some descriptive text -->
  <xsl:template match="node">
    <div>
      <xsl:value-of select="concat('[ ', @text, ': ', ' ]')" />
    </div>
  </xsl:template>

</xsl:stylesheet>

Output for your sample XML:

<div class="SideMenu">
  <div>[ Standard options: ]</div>
  <div>[ Specifying filenames: ]</div>
  <div>[ XML format: ]</div>
  <div>[ Creating custom templates: ]</div>
</div>

A nested menu is easily created, but admittedly I'm not really sure what you want. If you post the desired output, I can see to it.

Tomalak
A: 

Hi Peter,

If you can post the actual output you'd expect to be seeing then it would help, but I think you might want something like this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
  <xsl:template match="root">
    <div class="SideMenu">
      <!-- Loop through level 1 nodes -->
      <xsl:apply-templates select="//node[@depth=1]" mode="general" />
    </div>
  </xsl:template>

  <xsl:template match="node" mode="general">
    <xsl:choose>
      <!-- If the currently selected node, show all siblings -->
      <xsl:when test="@selected=1">
        <xsl:apply-templates select="../node" mode="only" />
      </xsl:when>
      <!-- Otherwise show just this node -->
      <xsl:otherwise>
        <xsl:apply-templates select="." mode="only" />
      </xsl:otherwise>
    </xsl:choose>
    <!-- Follow the breadcrumb down towards the currently selected node -->
    <xsl:apply-templates select="node[@breadcrumb=1]" mode="general" />
  </xsl:template>

  <xsl:template match="node" mode="only">
    <div>
      [ <xsl:value-of select="@text"/>: <xsl:value-of select="@depth"/> ]
    </div>
  </xsl:template>
</xsl:stylesheet>

If that doesn't give you what you want, feel free to drop a question on the DNNDoneRight forums, that's what they're there for!

MarkXA
A: 

Thanks both for your responses. Unfortunately your code wasn't quite what I needed (most likely due to the complex nature of what I'm after and my inability to explain my requirements) - but thanks to your example code, I've got a deeper understanding of XSL-T and created the following:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output method="html" />

 <xsl:template match="/">
    <div class="SideMenu">
      <!-- Loop through all L1 nodes -->
      <xsl:for-each select="Root/root/node">
        <xsl:if test="@enabled='1'" >
          <xsl:if test="@breadcrumb='1'" >
            <!-- Find active root node-->
            <!-- L0 nodes: Don't ever show (These are the horizontal tabs) -->

            <!-- Always show all children: L1 nodes -->
            <xsl:apply-templates select="child::node()[@url]" mode="BaseNode" ></xsl:apply-templates>

          </xsl:if>
        </xsl:if>
      </xsl:for-each>
    </div>
 </xsl:template>

  <!-- Template: Base node -->
  <xsl:template match="node" mode="BaseNode" >

    <xsl:choose>
      <xsl:when test="@breadcrumb='0'" >
        <!-- Non-breadcrumb L1 node -->
        <xsl:apply-templates select="." mode="NonShadedNode" ></xsl:apply-templates>

      </xsl:when>
      <xsl:when test="@breadcrumb='1'" >
        <!-- Child which is part of hierarchy -->
        <xsl:apply-templates select="." mode="ShadedNode" ></xsl:apply-templates>

        <!-- Drill down through hierarchy of active nodes-->
        <xsl:for-each select="descendant::node()[@url]">
          <xsl:if test="@breadcrumb='1'" >

            <!-- Is this the last node? If so, show children -->
            <xsl:choose>
              <xsl:when test="child::node()[@url]">
                  <!-- Not last node -->
                <xsl:apply-templates select="." mode="ShadedNode" ></xsl:apply-templates>

              </xsl:when>
              <xsl:otherwise>
                <!-- Last node: loop through all siblings -->

                <!-- THIS LOOP DOESN'T WORK CORRECTLY -->
                <xsl:for-each select="preceding-sibling::node()" >
                  <xsl:apply-templates select="." mode="ShadedNode" ></xsl:apply-templates>
                </xsl:for-each>

                <xsl:apply-templates select="." mode="SelectedNode" ></xsl:apply-templates>

                <xsl:for-each select="following-sibling::node()" >
                  <xsl:apply-templates select="." mode="ShadedNode" ></xsl:apply-templates>
                </xsl:for-each>

              </xsl:otherwise>
            </xsl:choose>

          </xsl:if>
        </xsl:for-each>

      </xsl:when>
    </xsl:choose>

  </xsl:template>

  <!-- Template: Non-shaded node -->
  <xsl:template match="node" mode="NonShadedNode" >
    <a>
      <xsl:attribute name="href">
        <xsl:value-of select="@url"/>
      </xsl:attribute>
      <xsl:attribute name="style">
        padding-left:<xsl:value-of select="@depth * 10"/>
      </xsl:attribute>
      <xsl:value-of select="@text"/>
    </a>
  </xsl:template>

  <!-- Template: Shaded node -->
  <xsl:template match="node" mode="ShadedNode" >
    <a class="SideMenu_Shaded">
      <xsl:attribute name="href">
        <xsl:value-of select="@url"/>
      </xsl:attribute>
      <xsl:attribute name="style">
        padding-left:<xsl:value-of select="@depth * 10"/>
      </xsl:attribute>
      <xsl:value-of select="@text"/>
    </a>
  </xsl:template>

  <!-- Template: Selected node -->
  <xsl:template match="node" mode="SelectedNode" >
    <a class="SideMenu_Selected">
      <xsl:attribute name="style">
        padding-left:<xsl:value-of select="@depth * 10"/>
      </xsl:attribute>
      <xsl:value-of select="@text"/>
    </a>
  </xsl:template>

</xsl:stylesheet>

This does exactly what I need, apart from the THIS DOESN'T WORK CORRECTLY section which for some reason also shows nodes which apart siblings... I'm currently looking at this and hoping it's a slight bug in my code.

Although if you can spot the problem, please let me know! :)

Here's the rendered HTML from the XSL-T:

<div class="SideMenu"><a href="/Home/Tester.aspx" style="&#xA;        padding-left:10">Tester </a><a class="SideMenu_Shaded" href="/Home/ChiefConstablesArea.aspx" style="&#xA;        padding-left:10">Chief Constables Area</a>
    Chief Constables Area
    Chief Constables Area
    <a class="SideMenu_Shaded" href="/Home/ChiefConstablesArea/ChiefsFocusGroups.aspx" style="&#xA;        padding-left:20">Chief's Focus Groups</a>
    <a class="SideMenu_Shaded" href="/Home/ChiefConstablesArea/AbouttheChief.aspx" style="&#xA;        padding-left:20">About the Chief</a>
    <a class="SideMenu_Shaded" href="/Home/ChiefConstablesArea/ChiefsOfficerGroup.aspx" style="&#xA;        padding-left:20">Chief's Officer Group</a>
    <a class="SideMenu_Selected" style="&#xA;        padding-left:20">Chief's Messages</a>
    <a class="SideMenu_Shaded" href="/Home/ChiefConstablesArea/ForceAwardsScheme.aspx" style="&#xA;        padding-left:20">Force Awards Scheme</a>
  <a href="/Home/ActiveForumLitetrial.aspx" style="&#xA;        padding-left:10">Active Forum Lite trial</a></div>
Peter Bridger
<xsl:for-each select="preceding-sibling::node()" >needed to be<xsl:for-each select="preceding-sibling::node()[@url]" >
Peter Bridger