Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

<xsl:sort, not sorting, everything else works! 2

Status
Not open for further replies.

SharkTooth

Programmer
Aug 12, 2004
29
US
I can't seem to get the <xsl:sort select element to work. I don't get an error it just doesn't sort. This is my first experence with XSLT so I'm guessing that I'm not using the <xsl:sort element correctly. Would you guys please take a look at what I'm doing below with the sort element? Everything else works fine, I just need to get the sort going.

I have an xml file formatted like so...
<?xml version="1.0" encoding="iso-8859-1"?>
<jobwatch>
<region desc="Areas of Pennsylvania, New Jersey and Delaware" id="N01" coords="368,155,363,131,399,123,407,128,407,138,416,142,421,160,407,161,401,148">
<thisAd>
<client>The Client Name 1</client>
<adCode>121212</adCode>
<magName>PXX1</magName>
<issueDate>02/05/2000</issueDate>
<imagePath>ads/121212.jpg</imagePath>
</thisAd>
<thisAd>
<client>The Client Name 2</client>
<adCode>232323</adCode>
<magName>PXX1</magName>
<issueDate>02/05/2001</issueDate>
<imagePath>ads/121214.jpg</imagePath>
</thisAd>

NEXT Region ...

This is my XSLT...

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="
<xsl:strip-space elements="*"/>
<xsl:eek:utput method="html" indent="no"/>

<xsl:variable name="COLS" select="2"/>
<xsl:variable name="ROWS" select="ceiling(count(/jobwatch/region[@id='N01']/thisAd) div $COLS)"/>
<xsl:variable name="CELLS" select="$COLS * $ROWS"/>

<xsl:template match="/jobwatch/region[@id='N01']">
<table width="100%" border="1" cellpadding="5">
<tr>
<xsl:call-template name="Column"/>
</tr>
</table>
</xsl:template>

<xsl:template match="/jobwatch/region[@id!='N01']">
</xsl:template>

<xsl:template name="Column">
<xsl:param name="NEXT" select="1"/>
<xsl:if test="$NEXT &lt;= $CELLS">
<xsl:variable name="POS" select="(($NEXT - 1) mod $COLS) * $ROWS + ceiling($NEXT div $COLS)"/>
<td valign="top" nowrap="true">
<xsl:choose>
<xsl:when test="thisAd[position() = $POS]">
<xsl:apply-templates select="thisAd[position() = $POS]/client">
<xsl:sort select="thisAd/client"/>
</xsl:apply-templates>
</xsl:when>
<xsl:eek:therwise>
<p></p>
</xsl:eek:therwise>
</xsl:choose>
</td>
<xsl:if test="$NEXT mod $COLS = 0 and $NEXT != $CELLS">
<xsl:text disable-output-escaping="yes">&lt;/tr&gt;&lt;tr&gt;</xsl:text>
</xsl:if>
<xsl:call-template name="Column">
<xsl:with-param name="NEXT" select="number($NEXT) + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>

<xsl:template match="thisAd/client">
<xsl:value-of select="."/>
</xsl:template>

</xsl:stylesheet>

Thanks for you help!
 
The xsl:sort appears to be asked to sort only one element at a time. This is a rather bizarre use of tail recursion. Please describe what you are trying to accomplish. It must be something more difficult than creating a table in HTML... [ponder]

Tom Morrison
 
Thanks Tom, I'm trying to display the data (client name) in two columns. That actually works, but the client names need to be sorted, right now they are displayed as entered in the XML. I thought of what you are saying and have tried to handle sorting elsewhere in the XSLT but haven't had any luck. It would be nice if I could sort the data right in the beginning then do the rest of the work but I haven't had any luck with that.

Thanks for your help!
Tim
 
Here is some luck!
Code:
<xsl:stylesheet version="1.0" xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform">[/URL]
 
<xsl:strip-space elements="*"/>
<xsl:output method="html" indent="no"/>
 
<xsl:variable name="COLS" select="2"/>
<xsl:variable name="ROWS" select="ceiling(count(/jobwatch/region[@id='N01']/thisAd) div $COLS)"/>
<xsl:variable name="CELLS" select="$COLS * $ROWS"/>
 
<xsl:template match="/">
    <table width="100%"  border="1" cellpadding="5">
            <xsl:call-template name="outputRow">
			    <xsl:with-param name="clientNodes">
					<xsl:for-each select="jobwatch/region[@id='N01']/thisAd/client">
					<xsl:sort select="./text()"/>
				    	<client><xsl:value-of select="."/></client>
					</xsl:for-each>
				</xsl:with-param>
			</xsl:call-template>
    </table>
</xsl:template>

<xsl:template name="outputRow">
	<xsl:param name="clientNodes"/>
	<xsl:choose>
	<xsl:when test="count($clientNodes/client) &gt;= $COLS">
        <tr>
		<xsl:for-each select="$clientNodes/client[position() &lt;= $COLS]">
        <td valign="top" nowrap="true"><xsl:value-of select="."/></td>
		</xsl:for-each>
        </tr>
		<xsl:if test="(count($clientNodes/client) - $COLS) &gt; 0">
			<xsl:call-template name="outputRow">
				<xsl:with-param name="clientNodes">
					<xsl:for-each select="$clientNodes/client[position() &gt; $COLS]">
				    	<client><xsl:value-of select="."/></client>
					</xsl:for-each>
				</xsl:with-param>
			</xsl:call-template>
		</xsl:if>
	</xsl:when>
	<xsl:otherwise>
        <tr>
		<xsl:for-each select="$clientNodes/client">
        <td valign="top" nowrap="true"><xsl:value-of select="."/></td>
		</xsl:for-each>
		<xsl:call-template name="outputEmptyCells">
			<xsl:with-param name="cellCount" select="$COLS - count($clientNodes/client)"/>
		</xsl:call-template>
        </tr>
	</xsl:otherwise>
	</xsl:choose>
</xsl:template>

<xsl:template name="outputEmptyCells">
	<xsl:param name="cellCount"/>
	<xsl:if test="$cellCount &gt; 0">
	<p></p>
	<xsl:call-template name="outputEmptyCells">
		<xsl:with-param name="cellCount" select="$cellCount - 1"/>
	</xsl:call-template>
	</xsl:if>
</xsl:template>

</xsl:stylesheet>


Tom Morrison
 
BTW, should your table get really long and messy, you may find an approach to alphabetizing a long list, with links intermixed, in thread426-1328351.

Tom Morrison
 
Sorry Tom, I was tied up in meetings all afternoon. Wow that is really hairy looking! I'm out of the office right now; I'll try this in the morning and let you know.
Thanks Again!
 
The original xsl looks much entangled, it shouldn't be...

[1]>I'm trying to display the data (client name) in two columns. That actually works, but the client names need to be sorted.
If that mean you have unsorted client name version worked with the original script, here is the approach using again <xsl:apply-templates> and most of the calling structure as yours which, as far as I am concerned, clearer and easier to maintain.
[tt]
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="
<xsl:strip-space elements="*"/>
<xsl:eek:utput method="html" indent="no"/>

<xsl:template match="/jobwatch/region[@id='N01']">
<table width="100%" border="1" cellpadding="5">
<xsl:apply-templates select="thisAd" mode="modulo_2" />
</table>
</xsl:template>

<xsl:template match="/jobwatch/region[@id!='N01']">
</xsl:template>

<xsl:template match="thisAd" mode="modulo_2">
<xsl:variable name="pos" select="position()" />
<xsl:choose>
<xsl:when test="position() mod 2 = 1">
<tr>
<td><xsl:value-of select="client" /></td>
<xsl:choose>
<xsl:when test="following-sibling::thisAd[1]/client">
<td><xsl:value-of select="following-sibling::thisAd[1]/client" /></td>
</xsl:when>
<xsl:eek:therwise>
<td><xsl:text>&#xa0;</xsl:text></td>
</xsl:eek:therwise>
</xsl:choose>
</tr>
</xsl:when>
<xsl:eek:therwise></xsl:eek:therwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
[/tt]
($col being 2 can be scripted into it if needed easily.)

The problem of simplistic generalization to sorted case by using this:
[tt]
<xsl:apply-templates select="thisAd" mode="modulo_2">
<xsl:sort select="client" data-type="text" />
</xsl:apply-templates>
[/tt]
would fail as the following-sibling's position will follow the document order unsorted. That is the major problem for sorted case with n-fixed-column.

But you could see the logic is of much clarity without loss of generality.

[2]> but the client names need to be sorted, right now they are displayed as entered in the XML.
If you want a sorted version of <client>, then it is not as simple. It is simple is you have one row one data ($col=1) or one row with all the data. But for 2 fixed column (or n fixed columns), it is not as simple in xslt 1.0. The best is to use the lacking functionality of node-set() implemented in practically all the major xslt processor (msxsl, saxon, xalan...). This is how it is applied.
[tt]
<xsl:stylesheet version="1.0" xmlns:xsl="
<xsl:strip-space elements="*"/>
<xsl:eek:utput method="html" indent="no"/>

<xsl:template match="/jobwatch/region[@id='N01']">

[blue]<xsl:variable name="nset_frag">
<xsl:for-each select="thisAd">
<xsl:sort select="client" data-type="text" />
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
<xsl:variable name="nset_root" select="msxsl:node-set($nset_frag)" xmlns:msxsl="urn:schemas-microsoft-com:xslt" />[/blue]

<table width="100%" border="1" cellpadding="5">
[blue]<xsl:apply-templates select="$nset_root/thisAd" mode="modulo_2" />[/blue]
</table>

</xsl:template>

<xsl:template match="/jobwatch/region[@id!='N01']">
</xsl:template>

<xsl:template match="thisAd" mode="modulo_2">
<xsl:variable name="pos" select="position()" />
<xsl:choose>
<xsl:when test="position() mod 2 = 1">
<tr>
<td><xsl:value-of select="client" /></td>
<xsl:choose>
<xsl:when test="following-sibling::thisAd[1]/client">
<td><xsl:value-of select="following-sibling::thisAd[1]/client" /></td>
</xsl:when>
<xsl:eek:therwise>
<td><xsl:text>&#xa0;</xsl:text></td>
</xsl:eek:therwise>
</xsl:choose>
</tr>
</xsl:when>
<xsl:eek:therwise></xsl:eek:therwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
[/tt]
Here, msxml2 extension node-set() is used. If you use any other major xsl processor, change the namespace msxsl accordingly. Now, it shows work sorted client, and the logic is of equal clarity. The excellent thing happens here is that the template (I use mode="modulo_2") is exactly the same as the case [1] (unsorted). The mis-alignment of the position() is resolved by the pass-through of the node-set().
 
Thanks to both of you! It works as expected but...
Now I need to remove the hard coded value in: <xsl:template match="/jobwatch/region[@id!='N01']">with the passed in variable: <xsl:template match="/jobwatch/region[@id=$publication]">, but I'm getting an error: "variables cannot be used within this expression.". Do you know of another way I can do that? I'm so close now!

Thanks
Tim
 
It depends on where the parameter comes about. Suppose it is a top level parameter picking up from the application calling the xsl document, this is how, one of the ways.
[tt]
<xsl:stylesheet version="1.0" xmlns:xsl="
<xsl:strip-space elements="*"/>
<xsl:eek:utput method="html" indent="no"/>

<xsl:param name="publication">
<xsl:value-of select="'N01'" />
</xsl:param>

<xsl:template match="/">
<xsl:apply-templates select="jobwatch/region">
<xsl:with-param name="sid" select="$publication" />
</xsl:apply-templates>
</xsl:template>

<xsl:template match="region">
<xsl:param name="sid" />
<xsl:choose>
<xsl:when test="@id=$sid">
<xsl:call-template name="action" />
</xsl:when>
<xsl:eek:therwise>
<!-- do nothing -->
</xsl:eek:therwise>
</xsl:choose>
</xsl:template>

<xsl:template name="action">
<!-- [blue]here you put all the stuff inside the original [@id='N01'] template[/blue] -->
</xsl:template>
</xsl:stylesheet>
[/tt]
 
Thanks tsuji! This works great! The only thing I'm not sure I'm going to get away with is in my original design the columns were ordered vertically down one column for half the number of records then to the top of the next column and down again.

Thanks again, I'm going to run out and take a couple of tutorials so I’m ready the next time something like this comes up.

Here's the code:
Code:
<xsl:stylesheet version="1.0" xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform">[/URL]

	<xsl:strip-space elements="*"/>
	<xsl:output method="html" indent="no"/>
	<xsl:param name="thisPage"/>
	<xsl:param name="ad"/>

	<xsl:param name="publication">
		<xsl:value-of select="'N01'" />
	</xsl:param>

	<xsl:template match="/">
		<xsl:apply-templates select="jobwatch/region">
			<xsl:with-param name="sid" select="$publication" />
		</xsl:apply-templates>
	</xsl:template>

	<xsl:template match="region">
		<xsl:param name="sid" />
		<xsl:choose>
			<xsl:when test="@id=$sid">
				<xsl:call-template name="action" />
			</xsl:when>
			<xsl:otherwise>
				<!--  do nothing -->
			</xsl:otherwise>
		</xsl:choose>
	</xsl:template>

	<xsl:template name="action">
		<xsl:variable name="nset_frag">
			<xsl:for-each select="thisAd">
				<xsl:sort select="client" data-type="text" />
				<xsl:copy-of select="." />
			</xsl:for-each>
		</xsl:variable>
		<xsl:variable name="nset_root" select="msxsl:node-set($nset_frag)" xmlns:msxsl="urn:schemas-microsoft-com:xslt" />

		<table width="100%"  border="0" cellpadding="5">
			<xsl:apply-templates select="$nset_root/thisAd" mode="modulo_2" />
		</table>

	</xsl:template>

	<xsl:template match="thisAd" mode="modulo_2">
		<xsl:variable name="pos" select="position()" />
		<xsl:choose>
			<xsl:when test="position() mod 2 = 1">
				<tr>
					<td>
						<a href='{$thisPage}?region={$publication}&amp;ad={adCode}#p'><xsl:value-of select="client" /></a>
					</td>
					<xsl:choose>
						<xsl:when test="following-sibling::thisAd[1]/client">
							<td>
								<a href='{$thisPage}?region={$publication}&amp;ad={adCode}#p'><xsl:value-of select="following-sibling::thisAd[1]/client" /></a>
							</td>
						</xsl:when>
						<xsl:otherwise>
							<td>
								<xsl:text>&#xa0;</xsl:text>
							</td>
						</xsl:otherwise>
					</xsl:choose>
				</tr>
			</xsl:when>
			<xsl:otherwise></xsl:otherwise>
		</xsl:choose>
	</xsl:template>
</xsl:stylesheet>
 
Tim and tsuji,

I normally avoid extensions in examples, since there is rarely enough information to be sure it safe to do so, and I have a concern about future readers of the thread.

However, node-set() is so useful that I should have used it. In the future, I will follow tsuji's example and explain the node-set() extension.

Tom Morrison
 
>The only thing I'm not sure I'm going to get away with is in my original design the columns were ordered vertically down one column for half the number of records then to the top of the next column and down again.

Sure, that's a matter of arithemtic. This is what need to change the renderment. (mode="module_2v")

[1] Change the corresponding line.

><xsl:apply-templates select="$nset_root/thisAd" mode="modulo_2" />

[tt]<xsl:apply-templates select="$nset_root/thisAd" mode="modulo_2[blue]v[/blue]">
<xsl:with-param name="tot" select="count($nset_root/*)" />
</xsl:apply-templates>
[/tt]
[2] The developed template (mode="modulo_2v") would be this.
[tt]
<xsl:template match="thisAd" mode="modulo_2[blue]v[/blue]">
[blue]<xsl:param name="tot" />
<xsl:variable name="jump" select="ceiling($tot div 2)" />[/blue]

<xsl:variable name="pos" select="position()" />
<xsl:choose>
<xsl:when test="[blue]$pos &lt;= $jump[/blue]">
<tr>
<td>
<a href='{$thisPage}?region={$publication}&amp;ad={adCode}#p'><xsl:value-of select="client" /></a>
</td>
<xsl:choose>
<xsl:when test="following-sibling::thisAd[[blue]$jump[/blue]]/client">
<td>
<a href='{$thisPage}?region={$publication}&amp;ad={adCode}#p'><xsl:value-of select="following-sibling::thisAd[[blue]$jump[/blue]]/client" /></a>
</td>
</xsl:when>
<xsl:eek:therwise>
<td><xsl:text>&#xa0;</xsl:text></td>
</xsl:eek:therwise>
</xsl:choose>
</tr>
</xsl:when>
<xsl:eek:therwise></xsl:eek:therwise>
</xsl:choose>
</xsl:template>
[/tt]
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top