RE: [xsl] Can't identify last ancestor node

Subject: RE: [xsl] Can't identify last ancestor node
From: "Michael Kay" <mike@xxxxxxxxxxxx>
Date: Tue, 28 Jun 2005 10:01:19 +0100
You haven't told us what the context node is for your code snippets -
presumably it's the <menu> element that you're testing? In that case they
don't make any sense at all. Looking at the ancestors of a node can't tell
you whether it has any following siblings, for example. (From your question,
I think you may have confused your ancestors with your descendants).

Best way to do this is probably to set a global variable to the last menu
element:

<xsl:variable name="last-menu" select="(//menu)[last()]"/>

and then when processing each menu element, test

<xsl:if test=". is $last-menu">  (XSLT 2.0)

<xsl:if test="generate-id(.) = generate-id($last-menu)"/>  (XSLT 1.0)

Michael Kay
http://www.saxonica.com/
 

> -----Original Message-----
> From: Mat Bergman [mailto:matbergman@xxxxxxxxx] 
> Sent: 28 June 2005 09:36
> To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
> Subject: [xsl] Can't identify last ancestor node
> 
> Using this XML data:
> 
> <menudata>
> 	<menu name="link1"/>
> 	<menu name="link2">
> 		<menu name="link2a"/>
> 		<menu name="link2b"/>
> 	</menu>
> </menudata>
> 
> 
> I am producing this HTML output:
> <ul>
> <li>link1</li>
> <li>link2
> 	<ul>
> 	<li>link2a</li>
> 	<li>link2b</li>
> 	</ul>
> </li>
> </ul>
> 
> I am using a convoluted stylesheet that tokenizes an
> attribute and generates the HTML output based on the
> attribute's value. My problem is identifying the last
> nested node, in this example "link2b", so that I can
> customize that node's output.
> 
> My complete stylesheet is below, but to summarize I
> made two attempts:
> 
> <xsl:for-each select="ancestor::menu">
> 	<xsl:if test="position()=last()">
> 	<xsl:text>Write custom end text here</xsl:text>
> 	</xsl:if>
> </xsl:for-each>
> 
> and
> 
> <xsl:if test="ancestor::menu[last()]">
> <xsl:text>Write custom end tag here</xsl:text>
> </xsl:if>
> 
> Both attempts wrote to each individual node, not just
> the last one. How can I tell my stylesheet to write
> one thing for the last <menu> node, and something else
> for the rest?
> 
> Here's the entire stylesheet:
> 
> <?xml version="1.0"?>
> <xsl:stylesheet version="1.0"
>               
> xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
> 	
> <xsl:output method="html" indent="yes"/>
> 
> <!-- siteID determines if a link is displayed for a
> specific cobrand -->
> <xsl:param name="siteID" select="2"/>
> 
> <!-- Displays navigation -->
> <xsl:template match="/">
> 
> 	<ul>
> 
> 	<!-- tokenize all <menu> nodes -->
> 	<xsl:for-each select="//menu">
> 
> 		<!-- Tokenize attributes to check if siteID is
> excluded -->
> 		<xsl:call-template name="tokenize">
> 			<xsl:with-param name="string" 
> select="@exsites"/>
> 			<xsl:with-param name="delimiters" select="','"/>
> 			<xsl:with-param name="level"
> select="'process-includeExclude'"/>
> 		</xsl:call-template>
> 
> 	</xsl:for-each>
> 
> 	</ul>
> 
> </xsl:template>
> 
> 
> <!-- Extract individual values from comma-delimited
> attribute -->
> <xsl:template name="tokenize">
> 	<xsl:param name="string" select="''" />
> 	<xsl:param name="delimiters" select="' &#x9;&#xA;'"
> />
> 	<xsl:param name="level" select="''" />
> 
> 	<xsl:call-template name="_tokenize-delimiters">
> 		<xsl:with-param name="string" select="$string" />
> 		<xsl:with-param name="delimiters"
> select="$delimiters" />
> 		<xsl:with-param name="level" select="$level" />
> 	</xsl:call-template>
> 
> </xsl:template>
> 
> 
> <xsl:template name="_tokenize-delimiters">
> 
> 	<xsl:param name="string" />
> 	<xsl:param name="delimiters" />
> 
> 	<xsl:param name="level" />
> 
> 	<xsl:param name="last-delimit"/> 
> 	<xsl:variable name="delimiter"
> select="substring($delimiters, 1, 1)" />
> 	
> 	<xsl:choose>
> 		<xsl:when test="not($delimiter)">
> 
> 		<!-- Sends individual attribute value for processing
> -->
> 		<xsl:call-template name="process-includeExclude" >
> 			<xsl:with-param 
> name="currentExsite"><xsl:value-of
> select="$string"/></xsl:with-param>	
> 		</xsl:call-template>
> 
> 	   </xsl:when>
> 
> 	<!-- process string until all tokens are separated
> -->
>    <xsl:when test="contains($string, $delimiter)">
>      <xsl:if test="not(starts-with($string,
> $delimiter))">
>        <xsl:call-template name="_tokenize-delimiters">
>          <xsl:with-param name="string"
> select="substring-before($string, $delimiter)" />
>          <xsl:with-param name="delimiters"
> select="substring($delimiters, 2)" />
>        <xsl:with-param name="level" select="$level" />
> 
>        </xsl:call-template>
>      </xsl:if>
>      <xsl:call-template name="_tokenize-delimiters">
>        <xsl:with-param name="string"
> select="substring-after($string, $delimiter)" />
>        <xsl:with-param name="delimiters"
> select="$delimiters" />
>        <xsl:with-param name="level" select="$level" />
>      </xsl:call-template>
>    </xsl:when>
>    <xsl:otherwise>
>      <xsl:call-template name="_tokenize-delimiters">
>        <xsl:with-param name="string" select="$string"
> />
>        <xsl:with-param name="delimiters"
> select="substring($delimiters, 2)" />
>        <xsl:with-param name="level" select="$level" />
>      </xsl:call-template>
>    </xsl:otherwise>
>  </xsl:choose>
> </xsl:template>
> 
> 
> <xsl:template name="process-includeExclude"
> match="menu">
> <xsl:param name="currentExsite" select="''" />
> 
> 	<!-- check if siteID is excluded or included -->
> 	<xsl:if test="$currentExsite!=$siteID">
> 	<xsl:if test="@incsites=$siteID or @incsites=0">
> 
> 		<!-- Write <li> tags -->
> 		<xsl:text
> disable-output-escaping="yes">&lt;li&gt;</xsl:text>		
> 
> 		<!-- Write list content -->
> 		<xsl:value-of select="@name"/>
> 
> 		<!-- Nested list - Write nested <ul> tags -->
> 		<xsl:if test="count(menu) > 0">
> 		<xsl:text
> disable-output-escaping="yes">&#10;&lt;ul&gt;&#10;</xsl:text>
> 		</xsl:if>
> 
> 		<!-- Write </li> tags for top-level and nested items
> -->
> 		<xsl:if test="count(menu) = 0">
> 		
> 		<xsl:choose>
> 
> <!-- This is where I am having trouble.  I want this
> to appear only at the last menu/menu node. Instead
> it's selecting all menu ancestors. -->
> 
> 			<!-- If it's the last sub-menu, close 
> with an </li>
> and a </ul> -->
> 			<xsl:when test="ancestor::menu[last()]">
> 				<xsl:text
> disable-output-escaping="yes">&lt;/li&gt;&lt;/ul&gt;
> 				</xsl:text>
> 			</xsl:when>
> 
> 			<!-- If it's not the last sub-menu, 
> close with an
> </li> -->
> 			<xsl:otherwise>
> 				<xsl:text
> disable-output-escaping="yes">&lt;/li&gt;
> 				</xsl:text>			
> 			</xsl:otherwise>
> 
> 		</xsl:choose>
> 
> 		</xsl:if>
> 
> 	</xsl:if>	
> 	</xsl:if>		
> 
> 
> </xsl:template>
> 
> </xsl:stylesheet>
> 
> 
> Thanks,
> 
> -Mat
> 
> 
> 
> 
> __________________________________________________
> Do You Yahoo!?
> Tired of spam?  Yahoo! Mail has the best spam protection around 
> http://mail.yahoo.com 

Current Thread