23

How-to break a for-each loop in XSLT?

Daniel Silveira
  • 41,125
  • 36
  • 100
  • 121
  • How about a code sample with source XML to explain why you need to use for-each? There is usually a better way using template matches. – Kev Jan 22 '09 at 17:43
  • Why would you want to? Perhaps for-each is not the construct you want in this case? – Valerion Jan 22 '09 at 17:23
  • Are you saying that BREAK statements aren't good at all? – Daniel Silveira Jan 22 '09 at 17:26
  • No no, just saying that the point of for-each is to apply something in a blanket way to every item, If you don't want to apply it to every item then it's the wrong thing to be using. – Valerion Jan 23 '09 at 14:15

6 Answers6

31

XSLT is written in a very functional style, and in this style there is no equivalent of a break statement. What you can do is something like this:

<xsl:for-each select="...nodes...">
    <xsl:if test="...some condition...">
        ...body of loop...
    </xsl:if>
</xsl:for-each>

That way the for-each will still iterate through all the nodes, but the body of the loop will only be executed if the condition is true.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 2
    In a "very functional style", there are also no for-each loops (although there are iterative constructs) and no if statements (although there is logic defined with guards and conditions). –  Aug 31 '13 at 16:10
19

Put the condition for stopping the "loop" in the select attribute of the for-each element. For instance, to "break" after four elements:

<xsl:for-each select="nodes[position()&lt;=4]">

To iterate up to but not including a node that satisfied some particular condition:

<xsl:for-each select="preceding-sibling::node[condition]">
Emil Vikström
  • 90,431
  • 16
  • 141
  • 175
9

XSLT isn't a procedural language; don't think of for-each as being a "loop" in the way you have a loop in Java. For-each is a way to apply a template to each of a bunch of items. It doesn't necessarily happen in a particular order, so you can't think of it as "apply this template to each of a bunch of items until such-and-such happens, then stop".

That said, you can use the select attribute to filter the results, so it becomes more like "apply a template to each of a bunch of items, but only if such-and-such is true of them".

If what you really want is "apply a template to each of a bunch of items, where such-and-such is true of them, but only to the first one this is true of", you can combine the select attribute with the position() function.

Jacob Mattison
  • 50,258
  • 9
  • 107
  • 126
  • 2
    It is not true that is used to apply a template to the selected nodes. Usually contains inline code that is performed on each of the selected nodes as the current node. – Dimitre Novatchev Jan 22 '09 at 21:40
  • 1
    And that "inline code" is called a *template*. From the XSLT 1.0 specification: *" The `xsl:for-each` instruction contains a template, which is instantiated for each node selected by the expression specified by the `select` attribute."* – michael.hor257k Oct 06 '22 at 09:55
5

A "break" from the body of an <xsl:for-each> XSLT instruction cannot be specified using a syntactic construct, however it can be simulated.

Essentially two techniques are discussed:

  1. Performing something inside the body of <xsl:for-each> only if a specific condition is satisfied. This can be improved if the condition can be specified in the select attribute of <xsl:for-each> -- in this case only the necessary nodes will be processed. See for example: https://stackoverflow.com/a/7532602/36305

  2. Specifying the processing not using <xsl:for-each> but with recursion. There are many examples of recursive processing with XSLT. See the code at: https://fxsl.sf.net/

The second method has the benefit of being able to perform the exit immediately, contrasted with the first method having to still perform many "empty cycles" even after the exit-condition has been satisfied.

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
1

I had a similar situation and here is the code I had written. For logical reasons, I couldn't fit in the other conditions with condition01.

<xsl:for-each select="msxsl:node-set($DATA6)[condition01]"> 
<xsl:choose>
<xsl:when test="not((condtion02 or  condition03) and condition04)">
--body of for loop 
</xsl:when>
</xsl:choose>
</xsl:for-each>
rjose
  • 557
  • 5
  • 13
  • Is there a reason `` is used instead of `` here? – Iyashi Mar 12 '19 at 16:22
  • You can use for simple if case scenario. As there is no else for I always tend to use . To answer your question, in the code here, we can use – rjose Apr 10 '19 at 09:32
0

Hello I kwow this is an old post but maybe it can help other developers. I have found a way to break a for each in XSLT it is not litteraly a break but if you see the code you will get it. As you know or not know you can use inline C# code in xslt. In this example i want to loop al the nodes and take the first NTE node with Value RC But if I get a node that differs from the NTE node i want to stop looking at the condition. So I set a global variable in C# code and I ask the value each time I go through a node:

    <xsl:value-of select="userCSharp:SetStopForeach('true')" />
<xsl:for-each select="following-sibling::node()">

        <xsl:if test="local-name()='NTE_NotesAndComments_3' and userCSharp:GetStopForeach()" >

        <xsl:for-each select="NTE_4_CommentType">
            <xsl:if test="(CE_0364_0_IdentifierSt)[text()=&quot;RC&quot;]"> 
        <ns0:RESULTAAT_COMMENTAAR>
                    <xsl:for-each select="../NTE_3_Comment">

                        <xsl:value-of select="./text()" />
                    </xsl:for-each>
                </ns0:RESULTAAT_COMMENTAAR> 
            </xsl:if>
        </xsl:for-each>
        </xsl:if>
        <xsl:if test="local-name()='ORC_CommonOrder'" >
            <xsl:value-of select="userCSharp:SetStopForeach('false')" />
        </xsl:if>
    </xsl:for-each>


    .....

    <msxsl:script language="C#" implements-prefix="userCSharp">
 <![CDATA[ 

    public bool StopForeach=false;
    public bool GetStopForeach() {
        return StopForeach;
    }
    public string SetStopForeach(bool aValue) {
         StopForeach=aValue;
         return "";
    }

      ]]> 
      </msxsl:script>
orophine
  • 9
  • 1
  • 3
    This is a Windows-specific solution. Standard XSLT is platform neutral, so this will not work in other environment, and will likely not work with other XSLT engines running in Windows. Also, it still appears you are iterating over every node vs truly breaking out of the loop. – ewh Oct 11 '13 at 16:35