tags:

views:

39

answers:

3

I currently have a <xsl:foreach> statement in my XSL processing all elements in this XML file. What I want to do though is process the first one separately to the rest. How can I achieve this?

Here is my current code:

<ul>
    <xsl:for-each select="UpgradeProgress/Info">
        <xsl:sort select="@Order" order="descending" data-type="number" lang="en"/>
        <li><xsl:value-of select="." /></li>
    </xsl:for-each>
</ul>
A: 

you can select your element with the position function in xpath

http://www.w3schools.com/xpath/xpath_functions.asp

remi bourgarel
How would I do that given my edited code? Thanks
Chris
+3  A: 

Assuming that you want to handle the first sorted element, this tests for the position inside of a choose statement and handles them differently:

<ul>
    <xsl:for-each select="UpgradeProgress/Info">
        <xsl:sort select="@Order" order="descending" data-type="number" lang="en"/>
        <li>
            <xsl:choose>
                <xsl:when test="position()=1">
                    <!-- Do something different with the first(sorted) Info element -->

                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="." />
                </xsl:otherwise>
            </xsl:choose>
        </li>
    </xsl:for-each>
</ul>
Mads Hansen
Exactly what I needed, thanks
Chris
See my answer for a more compact and flexible solution. :)
Dimitre Novatchev
Also see the discussion in the comments to my answer -- this is useful. :)
Dimitre Novatchev
+2  A: 

XSLT templates are your friends!

This 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:template match="/*">
     <ul>
       <xsl:apply-templates/>
     </ul>
 </xsl:template>

 <xsl:template match="num">
   <li>
     <xsl:value-of select="."/>
   </li>
 </xsl:template>

 <xsl:template match="num[1]">
   <li>
     <b><xsl:value-of select="."/></b>
   </li>
 </xsl:template>
</xsl:stylesheet>

when applied on this XML document:

<nums>
  <num>01</num>
  <num>02</num>
  <num>03</num>
  <num>04</num>
  <num>05</num>
  <num>06</num>
  <num>07</num>
  <num>08</num>
  <num>09</num>
  <num>10</num>
</nums>

produces the wanted special processing of the first (top) of the <num> elements:

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10

Do note, that you even don't have to use an <xsl:if> or <xsl:choose> in your code.

Use the enormous power of templates as much as possible.

Dimitre Novatchev
I like this a lot. How would one specify ranges? I.e. `<xsl:template match="num[1-4]">`
Chris
@Chris: `<xsl:template match="num[not(position() > 4)]">`
Dimitre Novatchev
+1 learned my bit from it - but it doesn't do the sorting, right?
Filburt
@Filburt: Thanks, for the sorting an `<xsl:sort>` instruction needs to be specified as a child of `<xsl:apply-templates>` -- this and all kinds of other decorations are possible.
Dimitre Novatchev
@Dimitre: If you use `<xsl:apply-templates><xsl:sort select="..."/></xsl:apply-templates>` in combination with `<xsl:template match="num[1]">`, you will *not* get the expected results, as match patterns in templates considers positions with regard to original document order, not with regard to order in the context node list. Try running your code with `<xsl:apply-templates><xsl:sort order="descending"/></xsl:apply-templates>` instead of `<xsl:apply-template/>`, and notice how the last output element gets the special processing.
markusk
@markusk: yes, I know this well, and also know what has to be done in this case, but this comment isn't relevant to the current question. Please, ask it as a separate question and I'd be glad to provide a short, but well-formatted answer. Hint: you need to do something additional, but the template definition(s) remain untouched. :)
Dimitre Novatchev
@Dimitre: As far as I can tell, the current question requests that the first node *in sort order* be processed separately. This is what the accepted answer (the one by Mads Hansen) does. Your answer does not, it processes the first node *in document order* separately. Do you disagree with my understanding of the current question? I fail to see how you can keep the definition `<xsl:template match="num[1]"/>` if you want to only treat sort order separately, not document order.
markusk
@markusk: this is not said in the text of the question. I see now that in the code the OP really performs a sort. The technique I describe is useful in this situation, too. Generally, one can have a multi-pass transformation and process any intermediate result (with the xxx:node-set() function applied to it in XSLT 1.0) with `<xsl:apply-templates>`. Of course, I would recommend multi-pass processing and using `<xsl:apply-templates>` vs. spaghetti-like code in a single template. Even when one sticks with the latter, very soon they'll discover that it becomes prohibitively complex. :)
Dimitre Novatchev