Re: [xsl] Referencing nodes from an out-of-scope xsl:for-each loop

Subject: Re: [xsl] Referencing nodes from an out-of-scope xsl:for-each loop
From: Adam Turoff <ziggy@xxxxxxxxx>
Date: Thu, 17 May 2001 21:49:49 -0400
On Thu, May 17, 2001 at 01:59:18PM -0700, Joel P Thornton wrote:
> I have this xml:
> 
> --
> <sql-recordset>
>   <row title="Sunshine Home" location="Seattle WA"/>
>   <row title="Value Village" location="Seattle WA"/>
>   <row title="Salvation Army" location="Tacoma WA"/>
> 
>   <display-columns>
>     <column label="Name"><B><sql-field name="title"/></B></column>
>     <column label="Location"><sql-field name="location"/></column>
>   </display-columns>
> </sql-recordset>
> --
> 
> And I want to write an XSL which will transform this into:
> 
> --
> <table>
>   <th>Name</th>
>   <th>Location</th>
> 
>   <tr>
>     <td><b>Sunshine Home</b></td><td>Seattle WA</td>
>   </tr>

...

> </table>
> --
> 
> 
> Currently, I <xsl:for-each> through the collection of <row> nodes, and then
> <xsl:for-each> through each of the <column>s for each row.  The problem is,
> I have a <xsl:template match="sql-field"> template which matches the
> <sql-field> tags within the <column> tags.  But I don't know how to get that
> template to "know" which row it is currently on.  I tried using
> <xsl:variable> but I'm being told that, within the sql-field template, the
> variable is out of scope.
> 
> Ideas?

First you need to get into a state where you have the tree fragment of
rows as well as the tree fragment of columns.  This can be done using
<xsl:call-template> for <row>, passing a tree fragment for <display-columns>
as a parameter.

Once you've cracked that nut, it's a simple case of referring to both
nodesets at the same time.  :-)

The really tricky part is respecting the bolding with 
     <column label="Name"><B><sql-field name="title"/></B></column>
as well as no formatting
     <column label="Location"><sql-field name="location"/></column>

It took some trickery to get that working.  You should consider a different
way for specifying HTML styles to be output, such as putting a style=''
attribute on <column> and passing it through to <td>.

	<xsl:template match="sql-recordset">
	 <xsl:variable name="display-columns" select="display-columns"/>
	 
	 <!-- table header (simple) -->
	 <table>
	  <tr>
	   <xsl:for-each select="display-columns/column/@label">
	    <th><xsl:value-of select="."/></th>
	   </xsl:for-each>
	  </tr>

	  <!-- table rows (passing the display format as a parameter -->

	  <xsl:for-each select="row">
	   <xsl:call-template name="display-row">
	    <xsl:with-param name="display-columns" select="$display-columns"/>
	   </xsl:call-template>
	  </xsl:for-each>
	 </table>
	</xsl:template>


	<!-- Emit the current <row/> as specified -->
	<xsl:template name="display-row">
	 <xsl:param name="display-columns"/>

	 <!-- save the tree fragment for the row we're processing -->
	 <xsl:variable name="row" select="."/>
	 <tr>

	  <!-- now loop over the column display order -->
	  <xsl:for-each select="$display-columns/column//sql-field">
	   <xsl:variable name="column" select="@name"/>
	   <td>

	    <!-- HACK: Respect <b> surrounding <sql-field> -->
	    <xsl:choose>
	     <xsl:when test="name(..) != 'column'">
	      <xsl:element name="{name(..)}">
	       <xsl:value-of select="$row/attribute::*[name()=$column]"/>
	      </xsl:element>
	     </xsl:when>
	     <xsl:otherwise>

	      <!-- standard case: just emit the data -->
	      <xsl:value-of select="$row/attribute::*[name()=$column]"/>
	     </xsl:otherwise>
	    </xsl:choose>
	   </td>
	  </xsl:for-each>
	 </tr>
	</xsl:template>


(I suppose you could do the same thing matching on <row> with a lot
of ancestor hacking, but I only thought of that after I got this pair
of templates working.)

Z.


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


Current Thread