Re: [xsl] Creating Hierachial menu

Subject: Re: [xsl] Creating Hierachial menu
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Wed, 17 Jul 2002 18:12:55 +0100
Hi Terry,

> I have done simiar things in the past where one of the generated
> nodes is a category and using keys() and generate-id() etc (the
> Meunchian? technique I think they call it) I have created a menu
> based on these categories, however this has always been a top level
> menu. Now I am trying to create a multi level menu system and I
> can't seem to make the logical leap using the same approach. Each
> image can be in more than one category - hence the duplicate
> resourceID's in the XML

Interesting problem. I don't think that the Muenchian Method will help
you here because you don't know the number of levels in advance, so
that makes it very difficult to create the keys that you'd need to use
it.

Instead, I think that you need to create a recursive template that
accepts a set of menus and a specified level. It should create the
menu item for the first of the passed menus, giving the text of the
menuItem element with the specified value for its hierLevel attribute,
then work out which other menus in the list have the same value for
their child menuItem element at the requisite level. Finally it should
move on to those menus that have a *different* value for their
relevant menuItem element child, to create menus at the same level for
them.

The following template does this, just creating text to get an
indented output. You'll probably want to change what it creates, but
hopefully this will get you part the way there.

<xsl:template match="MenuRoot" name="createMenus">
  <xsl:param name="menus" select="menu" />
  <xsl:param name="level" select="1" />
  <xsl:param name="indent" select="'&#xA;'" />
  <xsl:if test="$menus">
    <xsl:variable name="item"
                  select="$menus[1]/menuItem[@hierLevel = $level]" />
    <xsl:if test="$item">
      <xsl:value-of select="concat($indent, $item)" />
      <!-- call for the next level on those menus that share the value
           for their child menuItem -->
      <xsl:call-template name="createMenus">
        <xsl:with-param name="menus"
          select="$menus[menuItem[@hierLevel = $level] = $item]" />
        <xsl:with-param name="level" select="$level + 1" />
        <xsl:with-param name="indent" select="concat($indent, '  ')" />
      </xsl:call-template>
      <!-- call for the same level on those menus that don't share the
           value for their child menuItem -->
      <xsl:call-template name="createMenus">
        <xsl:with-param name="menus"
          select="$menus[menuItem[@hierLevel = $level] != $item]" />
        <xsl:with-param name="level" select="$level" />
        <xsl:with-param name="indent" select="$indent" />
      </xsl:call-template>
    </xsl:if>
  </xsl:if>
</xsl:template>

The biggest problem with this approach is that it involves visiting
each menu element lots of times, so you may well find that for your
1000 images it takes prohibitively long. Dimitre will probably have
some good ideas about how to speed it up.

You implied in your message that you could generate a different XML
structure. If you can, I suggest that you do -- XSLT 1.0's not very
good at grouping, and it's likely that the work involved creating a
structure for the XML that's easier for XSLT to deal with will end up
being less overall if you do it outside XSLT.

---

Interesting challenge for XSLT 2.0's grouping mechanisms here. You
still need the recursive call so that you can step down the levels,
but the grouping itself is a lot easier:

<xsl:template match="MenuRoot" name="createMenus">
  <xsl:param name="menus" select="menu" />
  <xsl:param name="level" select="1" />
  <xsl:param name="indent" select="'&#xA;'" />
  <xsl:for-each-group select="$menus"
                      group-by="menuItem[@hierLevel = $level]">
    <xsl:value-of select="concat($indent,
                                 menuItem[@hierLevel = $level])" />
    <xsl:call-template name="createMenus">
      <xsl:with-param name="menus" select="current-group()" />
      <xsl:with-param name="level" select="$level + 1" />
      <xsl:with-param name="indent" select="concat($indent, '  ')" />
    </xsl:call-template>
  </xsl:for-each-group>
</xsl:template>

Cheers,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread