views:

203

answers:

1

How do I apply a parameter to a select and order attribute in a xsl:sort element? I'ld like to do this dynamic with PHP with something like this:

$xsl = new XSLTProcessor();
$xslDoc = new DOMDocument(); 
$xslDoc->load( $this->_xslFilePath );
$xsl->importStyleSheet( $xslDoc );
$xsl->setParameter( '', 'sortBy', 'viewCount' );
$xsl->setParameter( '', 'order', 'descending' );

But I'ld first have to now how to get this to work. I tried the following, but it gives me a 'compilation error' : 'invalid value $order for order'. $sortBy doesn't seem to do anything either:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
<xsl:output method="xml" indent="yes"/>
<xsl:param name="sortBy" select="viewCount"/>
<xsl:param name="order" select="descending"/>
<xsl:template match="/">
    <media>
    <xsl:for-each select="media/medium">
    <xsl:sort select="$sortBy" order="$order"/>
        // <someoutput>
    </xsl:for-each>
    </media>
</xsl:template>
</xsl:stylesheet>
+3  A: 

You are close to the correct solution, but there are a few issues:

  1. <xsl:param name="sortBy" select="viewCount"/> This defines the $sortBy parameter as the value of the viewCount child of the current node (the document node). Because the top element is not named viewCount, the $sortBy parameter so defined has no value at all.

  2. <xsl:param name="order" select="descending"/> Ditto.

  3. <xsl:sort select="$sortBy" order="$order"/> Even if issues 1. and 2. above are fixed, this xslt instruction is still problematic. It specifies the value of the order attribute as the literal string '$order' -- not as the value of the parameter $order. The way to do this in XSLT is by using AVT (Attribute Value Template). Whenever we want a to specify that within an attribute value we want a particular string to be evaluated as an XPath expression, then this string must be surrounded by curly braces.

So, the order attribute should be specified as: order = '{$order}'.

Unfortunately, AVTs cannot be used for the select attribute (another rule from the XSLT spec).

The way to specify the value of the select attribute is a little-bit more tricky:

select='*[name()=$sortBy]' This says: sort by the child element, whose name is the same as the value of the variable $sortBy.

To put all this together, here is the corrected transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:strip-space elements="*"/>

 <xsl:param name="sortBy" select="'viewCount'"/>
 <xsl:param name="order" select="'descending'"/>

 <xsl:template match="/">
    <media>
      <xsl:for-each select="media/medium">
        <xsl:sort select="*[name()=$sortBy]" order="{$order}"/>

        <xsl:copy-of select="."/>
      </xsl:for-each>
    </media>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the following XML document:

<media>
 <medium>
   <viewCount>2</viewCount>
 </medium>
 <medium>
   <viewCount>1</viewCount>
 </medium>
 <medium>
   <viewCount>5</viewCount>
 </medium>
</media>

The correct result is produced:

<media>
   <medium>
      <viewCount>5</viewCount>
   </medium>
   <medium>
      <viewCount>2</viewCount>
   </medium>
   <medium>
      <viewCount>1</viewCount>
   </medium>
</media>
Dimitre Novatchev
+1 Thank you so much for the extended explanation. That is brilliant. This works perfectly! And now it makes sense. I've been banging my head against the wall these last couple of hours. Thank you.
fireeyedboy