Sunday, September 11, 2005

XSL sort - parameter driven

So this has been my other quest this weekend - how to cause different XSL sorts to occur based on passed parameters - suprisingly this is not as easy as it sounds.

Tag syntax

The first difficulty arises from the syntax of the XSL sort tag:

<xsl:sort
select="some text that picks a node"
data-type="some text that defines a data type"
order="some text that defines a sort order"/>



The sort "command" accepts the three parameters shown - only "select" is compulsory - the other two have default values ("text" and "ascending")

The "data-type" and "order" are easy to paramterise. Assuming that you have XSL variables "type" and "order" that have been set - to the values "number" and "descending" - the following fragment shows how a decreasing numeric sort by "payrate" could be requested. We assume that the nodes being matched have a "payrate" child:

<xsl:sort
select="payrate"
data-type="{$type}"
order="{$order}"/>


Note that the {} brackets force evaluation of the variable that is indicated with the "$".

The problem is though that you cannot do the same with the "select" parameter. The select parameter must be a node-set - in other words (in the context of a sort) an expression that leads the XSLT processor to identify a single node. Examples of such expressions are:

text() - the content of the current node
age - the content of the "age" child of the current node
payrate/tax - the content of the "payrate/tax" grandchild of the current node

Most XSLT processors (the program - usually on the server) that "applies" your XSLT transformation do not know how to convert text in a parameter into a node-set. In other words the following will fail even if the variable "target" has the value "payrate":

<xsl:sort
select="{$target}"
data-type="{$type}"
order="{$order}"/>

The answer is to write a slightly more complex "select" expression that includes a component that does accept a text value:

<xsl:sort
select="(*|*/*)[name()=$target]"
data-type="{$type}"
order="{$order}"/>


All the "*" stuff selects the current node or any descendents and the square bracket says that the name of the node must match the parameter value - if the node has a child named "payrate" then setting "target" to "payrate" will sort by the value of this element.

All this is fine so long as the sort "target" can be uniquely defined by a node name. What if "payrate" contains multiple "tax" elements (say federal, state and local) and we want to sort by one of these:

<xsl:sort
select="payrate/tax[position()=$target]"
data-type="{$type}"
order="{$order}"/>


This example expects "target" to be set to 1, 2, 3... to indicate which payrate/tax child is to be applied to this sort.

Conditional sorting

This leads to the second problem - how to apply one of these sorts in preference to any other.

The problem arises from the fact that "xsl:sort" can only be the child of "xsl:for-each" or "xsl:apply-templates" - we simply are not allowed to use "xsl:if" or "xsl:choose" to select a sort to apply.

The answer is that multiple sorts can be defined inline:

<xsl:sort select="(*|*/*)[name()=$target]" data-type="{$type}" order="{$order}"/>
<xsl:sort select="payrate/tax[position()=$target]" data-type="{$type}" order="{$order}"/>

Ordinarily this would be used to apply nested sorting (ie sort by one field and then by another) but in this case, so long as only one of the "select" expressions evaluates to a non-empty node-set, a single sort will be applied.

Obviously this must be used with care - consider whether more than one of the "select" expressions will succeed - if so try something else.

13 Comments:

Anonymous Wesley said...

This came in quite handy today...thanks for sharing!

8:42 AM  
Blogger Neil said...

Hi,
I am running into a dead end. Have select working dynamically but oddly the parameterized order attribute won't play ball and always sorts descending.

any help will be appreciated and compensated too if it works out and takes any time.

5:03 PM  
Anonymous André said...

Excellent. The solution described for the select="$someparameter" in xsl sort, worked fine. It saved my (working) day. Thanks for sharing!

12:15 PM  
Anonymous Anonymous said...

thank you thank you thank you
just what i needed after one hour of struggling

1:47 PM  
Anonymous n4h0y said...

AWESOME!!! Great Help

1:59 AM  
Anonymous Anonymous said...

It isn't hard at all to start making money online in the undercover world of [URL=http://www.www.blackhatmoneymaker.com]blackhat blog[/URL], Don’t feel silly if you don't know what blackhat is. Blackhat marketing uses alternative or misunderstood methods to produce an income online.

7:08 PM  
Anonymous Anonymous said...

Thank you, really useful!

11:02 PM  
Anonymous Anonymous said...

Thank you! Excellent solution! I was struggling with passing sort item as a parameter for a few days. Even w3c documentation didn't help.
Thanks a lot!!!

3:20 AM  
Anonymous Anonymous said...

thank you :) looked lots of places for something i could just copy and paste :)

10:32 AM  
Blogger Unknown said...

Saved me after an hour of trying things. I can't imagine why the online docs don't describe this unusual behavior of 'sort select'.

8:49 AM  
Anonymous Anonymous said...

took me 3 hours to track down what I was looking for!

Now, how to do the same with the root...

THanks

6:16 PM  
Anonymous Anonymous said...

Thanks for sharing your thoughts on dumb. Regards

Here is my webpage ... hip to waist ratio calculator

11:58 AM  
Anonymous Anonymous said...

thnks a lot sir.

11:43 PM  

Post a Comment

<< Home