Jump to content

How do I create a hierarchical Navigation Menu


javajoemorgan
 Share

Recommended Posts

I've got to create a navigational menu from a document. Certain elements form a hierarchy, in which they must be indented in the nav menu. I am using unordered lists. So, to simplify, here is an XML:

<ANYTHING>    <ANYTHING-ELSE>       <MAYBE-MORE-MAYBE-NOT>         <HEADING>           <TITLE>Title 1</TITLE>           <HEADING>             <TITLE>Subtitle 1-1</TITLE>             <HEADING>               <TITLE>Subtitle 1-1-1</TITLE>             </HEADING>             <HEADING>               <TITLE>Subtitle 1-1-2</TITLE>             </HEADING>           </HEADING>         </HEADING>         <HEADING>           <TITLE>Title 2</TITLE>           <HEADING>             <TITLE>Subtitle 2-1</TITLE>           </HEADING>         </HEADING>         <HEADING>           <TITLE>Title 3</TITLE>         </HEADING>       </MAYBE-MORE-MAYBE-NOT>    </ANYTHING-ELSE></ANYTHING>

Of course, there may be many sub-titles and sub-sub titles, down to any level.I need it to come out like this:

<ul>  <li>HEADINGS</li>    <ul>      <li>Title 1</li>        <ul>        <li>Subtitle 1-1</li>        <ul>           <li>Subtitle 1-1-1</li>           <li>Subtitle 1-1-2</li>        </ul>      </ul>    </ul>    <ul>      <li>Title 2</li>        <ul>        <li>Subtitle 1-1</li>      </ul>    </ul>    <ul>      <li>Title 2</li>      </ul></ul>

I need ideas....A couple of key points. Though the HEADING and TITLE elements are consistent, their containers are not, nor is the level on which they start. All I'm interested in is the HEADING and it's TITLE for output, but I must have it nested properly.

Link to comment
Share on other sites

Let me start off by saying what you want to produce is NOT valid XHTML, and I suppose you wouldn't like that. Your ULs will have to be nested in the LIs.Try something like:

<xsl:template match="/ANYTHING/ANYTHING-ELSE/MAYBE-MORE-MAYBE-NOT"><html><head><title>Anything</title></head><body><xsl:apply-templates select="HEADING"/></body></html></xsl:template><xsl:template match="HEADING"><ul>	<xsl:apply-templates select="TITLE"/></ul></xsl:template><xsl:template match="TITLE"><li>	<xsl:apply-templates/>	<xsl:apply-templates select="../HEADING"/></li></xsl:template>

Link to comment
Share on other sites

Let me start off by saying what you want to produce is NOT valid XHTML, and I suppose you wouldn't like that. Your ULs will have to be nested in the LIs.
Maybe I typed something wrong... I do want valid XHTML.
Try something like:
<xsl:template match="/ANYTHING/ANYTHING-ELSE/MAYBE-MORE-MAYBE-NOT">

I'm close to what you suggested. Here's my main problem. I have no idea what level the first HEADING will be on. It generally starts after 2, but those 2 could be anything at all, and not consistent within the XML documents for which this transform is designed. The first HEADING element could be nested 4 deep. So I was thinking more along the lines of:
<xsl:template match="//HEADING"/>

What does the

<xsl:apply-templates match="../HEADING">

do? I read it to mean it processes the parent of TITLE, or the HEADING the TITLE is within. My mind twists that into something recursive.

Link to comment
Share on other sites

What I gave you SHOULD work regardless of the level the first heading is in. If it doesn't, then whatever is before it should be processed separately, until it reaches a point where it can match that template. Eliminating the "select" attribute on the first <xsl:apply-templates/> is another way."match" is a little different than a "select", so what you were thinking is pretty much the same as what I did.What

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

does (and please note, we use SELECT, not MATCH) is to go a step back from the current node ('TITLE'), and then select all HEADING elements from that point on, applying the appropriate template. If there are no such nodes, no template will be applyed. Thus, a UL won't be created if there's no HEADING element next to the TITLE.

Link to comment
Share on other sites

What I gave you SHOULD work regardless of the level the first heading is in. If it doesn't, then whatever is before it should be processed separately, until it reaches a point where it can match that template. Eliminating the "select" attribute on the first <xsl:apply-templates/> is another way.
Hmm..... Either I'm confused or not clearly explaining my situation. The XML could be:<HALLOWEEN><MONSTERS><HEADING>...or<DUCKS><FISH><SNAKES><HEADING>...or<KLINGONS><SPOCK><KIRK><KHAN><HEADING>....That is, I have no clue what may precede the beginning of the structure I'm trying to pull out. For the nav menu, it is unimportant. What is consistent is that whatever level the first <HEADING> element is on is the minimum level for any other <HEADING> element. However, <HEADING> elements are then nested within other <HEADING> elements up to 5 levels deep.Of course, then, within the Nav menu, each lower level <HEADING> needs to be nested within its parent.Make sense? Or did you already answer and I just can't understand?
Link to comment
Share on other sites

I think I see your point.Try to simply remove the first template, leaving only:

<xsl:template match="HEADING"><ul>	<xsl:apply-templates select="TITLE"/></ul></xsl:template><xsl:template match="TITLE"><li>	<xsl:apply-templates/>	<xsl:apply-templates select="../HEADING"/></li></xsl:template>

The first HEADING will be matched regardless of it's location in the source, but know that with this way, no other element is going to be created. Your root element of the output is going to be a "ul".

Link to comment
Share on other sites

OK.. that's not working... of course, my situation is a bit more complex.. .and my XMLs are too large to post here.Is there a way do to it more brute force:

<xsl:for-each select="//HEADING">    <ul>         <li><a href="#{@ID}"><xsl:value-of select="TITLE"/></a></li>                  <xsl:if test="descendent::count() > 0">             this is where I get lost.... what do I put here         </xsl:if>             </ul></xsl:for-each>

Your root element of the output is going to be a "ul".
That's OK. I have to process two transforms against the same XML. One for the page, one for the nav. These two results are then put into the final output document.
Link to comment
Share on other sites

Listen... it may not be pretty, but I've almost got it. Is it at all possible to write out my <UL> tag without also writing out my </UL> tag?????Here is my XSLT:

<?xml version="1.0"?><!-- =============================================================================This style sheet transforms Bulletin and Article data into a navigation menu================================================================================== --><xsl:stylesheet version="2.0"     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"    xmlns:xsd="http://www.w3.org/2001/XMLSchema"    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"    xmlns:dp="http://www.datapower.com/extensions" extension-element-prefixes="dp"    exclude-result-prefixes="#all">    <!-- =============================================================================================         This transform creates the navigation menu division for the document.    ================================================================================================== -->    <xsl:template match="/">            <dp:set-variable name="'var://context/xform/lastLevel'" value="'0'"/>            <p><xsl:value-of select="//DOC-HEAD[1]"/></p>            <ul class="clsHeadFive">                 <li>HEADINGS</li>                 <ul>                     <li><a href="#top">ARTICLE BEGINNING</a></li>                     <xsl:for-each select="//INFO-OBJ">                         <xsl:call-template name="INFO-OBJ">                             <xsl:with-param name="element" select="current()"/>                         </xsl:call-template>                     </xsl:for-each>                 </ul>            </ul>            <ul class="clsHeadFive">                 <li>FIGURES</li>                 <ul>                     <xsl:for-each select="//FIGURE">                         <xsl:call-template name="FIGURE">                             <xsl:with-param name="element" select="current()"/>                         </xsl:call-template>                     </xsl:for-each>                 </ul>            </ul>            <ul class="clsHeadFive">                 <li>TABLES</li>                 <ul>                     <xsl:for-each select="//TABLE">                         <xsl:call-template name="TABLE">                             <xsl:with-param name="element" select="current()"/>                         </xsl:call-template>                     </xsl:for-each>                 </ul>            </ul>    </xsl:template>    <!-- =============================================================================================         Transforms INFO-OBJ elements.  These contain a nesting hierarchy in which each different         level is presented differently, thus the need for the count of ancestor INFO-OBJ elements.           The CSS file currently contains only five levels for this.    ================================================================================================== -->    <xsl:template name="INFO-OBJ">        <xsl:param name="element"/>        <xsl:if test="dp:variable('var://context/xform/lastLevel') > count(ancestor::INFO-OBJ)">            <dp:set-variable name="'var://context/xform/lastLevel'" value="count(ancestor::INFO-OBJ)"/>            <!-- HOW DO I WRITE OUT JUST THE BEGINNING <UL> TAG HERE -->              </xsl:if>        <xsl:if test="dp:variable('var://context/xform/lastLevel') < count(ancestor::INFO-OBJ)">            <dp:set-variable name="'var://context/xform/lastLevel'" value="count(ancestor::INFO-OBJ)"/>            <!-- HOW DO I WRITE OUT JUST THE ENDING <UL> TAG HERE -->              </xsl:if>        <li class="clsHeadFive">            <a href="#{@ID}">                <xsl:value-of select="local-name(preceding-sibling::element[position()-1])"/>                A:<xsl:value-of select="count(ancestor::INFO-OBJ)"/>                PS:<xsl:value-of select="count(preceding-sibling::INFO-OBJ)"/>                FS:<xsl:value-of select="count(following-sibling::INFO-OBJ)"/>                D:<xsl:value-of select="count(descendant::INFO-OBJ)"/>                =<xsl:value-of select="TITLE"/>            </a>        </li>    </xsl:template>    <!-- =============================================================================================         Creates a reference to a FIGURE, however, this opens the figure in a window rather than          having if reference a FIGURE within the doc    ================================================================================================== -->    <xsl:template name="FIGURE">        <xsl:param name="element"/>        <li>            <xsl:choose>                <xsl:when test="GRAPHIC[@GRAPHICTYPE='GIF']">                    <a class="clsGraphicLink" href="{GRAPHIC/@GRAPHICFILEPATH}" target="_blank">                        Fig. <xsl:number format="1" level="any"/>: <xsl:value-of select="CAPTION"/>                    </a>                </xsl:when>                <xsl:when test="GRAPHIC[@GRAPHICTYPE='SVGZ']">                    <a class="clsGraphicLink" href="java script:showSVG('{GRAPHIC/@GRAPHICFILEPATH}'); return false;">                        Fig. <xsl:number format="1" level="any"/>: <xsl:value-of select="CAPTION"/>                    </a>                </xsl:when>            </xsl:choose>        </li>    </xsl:template>    <!-- =============================================================================================          Creates a reference to a TABLE in the doc    ================================================================================================== -->    <xsl:template name="TABLE">        <xsl:param name="element"/>        <li><a href="#{@ID}"><xsl:value-of select="TITLE"/></a></li>    </xsl:template></xsl:stylesheet>

The only thing I'm having a problem with is the INFO-OBJ elements... they are my HEADING elements.... I've got them all listing out, but this tries to nest them based upon their level in the document. How to I write out the <UL> tag, and then the </UL> tag above???? If I can do that, it's done!The "dp:" elements are special elements within an IBM DataPower appliance... it allows me to have non-constant variables.

Link to comment
Share on other sites

I'm very close. To initiate the call for the first HEADING into the structure, I simply do this:

    <xsl:template match="HEADING">        <li class="clsHeadFive"><a href="#{@ID}"><xsl:value-of select="TITLE"/></a></li>        <xsl:if test="child::HEADING">            <ul>                <xsl:apply-templates select="child::HEADING"/>            </ul>        </xsl:if>    </xsl:template>

However, it isn't "navigating out" of the elements correctly, which I thought would be resolved by your suggestion:

01 DESCRIPTION02   DRIVE SHAFT ALIGNMENT03     FRONT DRIVE SHAFT04     REAR DRIVE SHAFT05   UNIVERSAL JOINTS06 MAINTENANCE07   UNIVERSAL JOINTS08   LUBRICANTS09 TROUBLESHOOTING10   VIBRATION HARSHNESS11 REMOVAL & INSTALLATION12   FRONT & REAR DRIVE SHAFT13     REMOVAL14     DISASSEMBLY15     REASSEMBLY16     INSTALLATION17   REAR DRIVE SHAFT WITH FLEXIBLE COUPLING18     REMOVAL19     DISASSEMBLY & ASSEMBLY20     INSTALLATION21   FLEXIBLE COUPLING22   UNIVERSAL JOINTS23 TORQUE SPECIFICATIONS

This is what I am getting:

01 DESCRIPTION02   DRIVE SHAFT ALIGNMENT03     FRONT DRIVE SHAFT04     REAR DRIVE SHAFT05   UNIVERSAL JOINTS02 DRIVE SHAFT ALIGNMENT03   FRONT DRIVE SHAFT04   REAR DRIVE SHAFT03 FRONT DRIVE SHAFT07 UNIVERSAL JOINTS10 VIBRATION HARSHNESS12 FRONT & REAR DRIVE SHAFT13   REMOVAL14   DISASSEMBLY15   REASSEMBLY16   INSTALLATION13 REMOVAL18 REMOVAL11 REMOVAL & INSTALLATION

Any ideas or explanations would be greatly appreciated.

Link to comment
Share on other sites

It's not useful to try to emulate a functional behaviour when such is already available.You're thinking too procedurally. And to get the full power of XSLT, you should think functionally.Here's a stylesheet that WILL work. I've tested it against your XML sample, and even altered your sample a little to at least try to ensure it will work in every case:

<?xml version="1.0"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">	<xsl:template match="text()"/>	<xsl:template match="*[HEADING and name() != 'HEADING']">		<ul>			<xsl:apply-templates/>		</ul>	</xsl:template>	<xsl:template match="HEADING">		<li>			<xsl:apply-templates select="TITLE"/>		</li>	</xsl:template>	<xsl:template match="TITLE">		<xsl:value-of select="."/>		<xsl:if test="../HEADING">			<ul>				<xsl:apply-templates select="../HEADING"/>			</ul>		</xsl:if>	</xsl:template></xsl:stylesheet>

And the XHTML it generates is valid, unlike the one you were close to produce.(and btw, in case you ever need to generate code procedurally, there's DOE, but that's not a good thing)

Link to comment
Share on other sites

  • 2 weeks later...
It's not useful to try to emulate a functional behaviour when such is already available.You're thinking too procedurally. And to get the full power of XSLT, you should think functionally.Here's a stylesheet that WILL work......
I don't know if you are still monitoring, but, this pretty much gets it done. I have some artifacts I suspect I can get rid of, but you're help and patience have been invaluable.I'm not sure this is thinking functionally either... but it is clear I can't think this way. In any case, though I can't really explain why it is working (because it seems to go forwards and backwards), it works and that's all I care about now.... I'll lock it up in a hardened case and never touch it again! thanks a million!
Link to comment
Share on other sites

Thinking functionally goes exactly in the way that there's no "forwards and backwards". There's a match, and no match. A match may or may not check if there are other matches in a certain context (by default in XSLT - further down the XML tree).Let's put it like this. You want to give instructions to someone as to how to disassamble a computer from all of it's parts. Thinking procedurally, you say

Unscrew the two screws from the back of one of the cover, and do the same for the other cover. Pull out both covers. Unscrew all screws from the back (i.e. where the boards are screwed), and pull the boards out. Slide the RAM holders, pull the RAMs out. Plug out the IDE cables, the SATA cables, and case cables, including the ones coming from the power supply. Pull out the CD/DVD-ROM, and HDD out by the sides. Unscrew the screws on the motherboard, pull the motherboard sideways and up. Rotate the four... um... pins (I'm not exacty sure what those things are called) around the CPU fan and pull them out. Unscrew the screws at the power supply, pull it sideways (from the back of the box in the direction to the front), and pull it out.
OK, now wasn't that just a pain in the @$$ to read? If you think functionally, you could say
Unless otherwise said, pull out everything. When you find a screw, unscrew it. When you find a cable, unplug it. When you find a "boxy" device (HDD, CD/DVD-ROM or a power supply) or a motherboard, pull it out by the sides and check if you can do anything else. When you find a RAM, slide its holders and check if you can do anything else. When you find a CPU fan, rotate its pins and check if you can do anything else.
(the "anything else" in our case is only "pull it out", as said in the first sentence, but it could be any number of things)Getting back on your menu, when we find a TITLE, we check if there are any HEADING siblings to the TITLE, and if there are, we create a "ul" element, after which we check if we can do "anything else", but limting ourselves to what we could do to sibling HEADING elements, rathar than anything at all. That "anything else" in that case becomes what we usually do to HEADING elements., which is creating a "li" element, and we see if we can do anything else, limiting ourselves only to the "TITLE" elements at that HEADING's level.
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...