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

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

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.

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()<=4]">
To iterate up to but not including a node that satisfied some particular condition:
<xsl:for-each select="preceding-sibling::node[condition]">

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.

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:
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
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.

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>

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()="RC"]">
<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>

Related

XSLT Check if case occured in for-each and use the information

I don't have an approach for this, since I'm not able to change values of variables in XSLT.
Hopefully you could provide a proper approach for the following problem:
This is an example xml-node, to which I'll quote to explain the problem:
<information>
<uselessNode1></uselessNode1>
<uselessNode2></uselessNode2>
<importantNode></importantNode>
</information>
What I essentially need here is to go through all the child nodes of the information node.
The importantNode is optional and the thing what I've to detect is whether this node is there or not.
If the importantNode is there it'll processed within the for-each loop and everything is fine.
In case of not having the importandNode, I've to show it's absence outside of the for-each loop.
So, if having following for-each loop in a template for the information node:
<xsl:for-each select="*">
<xsl:choose>
<xsl:when test="not( name() = 'importantNode')">
USELESS
</xsl:when>
<xsl:otherwise>
IMPORTANT
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
The output is "USELESS USELESS IMPORTANT". And if the important node is not there the output is
"USELESS USELESS". What I actually need to have instead of "USELESS USELESS" is a prompt, which shows that the important node is not there, e.g. "USELESS USELESS IMPORTANT_MISSING".
I tried to set a variable in the for-each loop if going in the -case and trying to
check this outside of the for-each loop throws an error, that the variable $importantFound is not declared or out of it's scope. I though declaring it before the for-each loop would help. Since I can't declare the variable before the for-each loop and change it's value in the otherwise case inside of the loop, I don't know how to solve this dilemma.
I hope the explanation of my problem was clear enough to understand.
Thank You all in advance.
In case of not having the importandNode, I've to show it's absence outside of the for-each loop.
So why can't you do exactly that - for example:
<xsl:template match="information">
<xsl:for-each select="*">
<xsl:choose>
<xsl:when test="not(self::importantNode)">USELESS </xsl:when>
<xsl:otherwise>IMPORTANT </xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:if test="not(importantNode)">IMPORTANT_MISSING</xsl:if>
</xsl:template>

Looping through an xpath getting the right iteration each time in xslt 2

I have a condition where I need to loop atleast once and so I have the following xsl code. However, this doesnt work as it always gets the last iterations value. How can I tweak this so it gets the right iteration on each loop?
<xsl:variable name='count0'>
<xsl:choose>
<xsl:when test='count($_BoolCheck/BoolCheck[1]/CheckBoolType) = 0'>
<xsl:value-of select="1"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select='count($_BoolCheck/BoolCheck[1]/CheckBoolType)'/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:for-each select="1 to $count0">
<xsl:variable name='_LoopVar_2_0' select='$_BoolCheck/BoolCheck[1]/CheckBoolType[position()=$count0]'/>
<e>
<xsl:attribute name="n">ValueIsTrue</xsl:attribute>
<xsl:attribute name="m">f</xsl:attribute>
<xsl:attribute name="d">f</xsl:attribute>
<xsl:if test="(ctvf:isTrue($_LoopVar_2_0/CheckBoolType[1]))">
<xsl:value-of select=""Value True""/>
</xsl:if>
</e>
</xsl:for-each>
The xml file is as follows:
<BoolCheck>
<CheckBoolType>true</CheckBoolType>
<CheckBoolType>false</CheckBoolType>
<CheckBoolType>1</CheckBoolType>
<CheckBoolType>0</CheckBoolType>
<CheckBoolType>True</CheckBoolType>
<CheckBoolType>False</CheckBoolType>
<CheckBoolType>TRUE</CheckBoolType>
<CheckBoolType>FALSE</CheckBoolType>
</BoolCheck>
In this case I need to iterate through each iteration of CheckBoolType and produce a corresponding number of values. However, in the above example if there were no CheckBoolType iterations I would still like the iterations to enter the for-each loop atleast once. i hope that clarifies it a little more.
First observation: your declaration of $count0 can be replaced by
<xsl:variable name="temp" select="count($_BoolCheck/BoolCheck[1]/CheckBoolType)"/>
<xsl:variable name="count0" select="if ($temp=0) then 1 else $temp"/>
(Sorry if that seems irrelevant, but my first step in debugging code is always to simplify it. It makes the bugs much easier to find).
When you do this you can safely replace the predicate [position()=$count0] by [$count0], because $count0 is now an integer rather than a document node. (Even better, declare it as an integer using as='xs:integer' on the xsl:variable declaration.)
But hang on, $count0 is the number of elements being processed, so CheckBoolType[$count] will always select the last one. That's surely not what you want.
This brings us to another bug in your code. The value of the variable $_LoopVar_2_0 is an element node named CheckBoolType. The expression $_LoopVar_2_0/CheckBoolType[1] is looking for children of this element that are also named CheckBoolType. There are no such children, so the expression selects an empty sequence, so the boolean test is always false.
At this stage I would like to show you some correct code to achieve your desired output. Unfortunately you haven't shown us the desired output. I can't reverse engineer the requirement from (a) your incorrect code, and (b) your prose description of the algorithm you are trying to implement.

XSLT for-each with inner if statement doesn't affect position starting by 1, it starts by 5

I have a for-each loop where I have added an inner if statement to filter over my application. The application that is related is not directly referenced within the nodes I am looping over.
It is rather formularfield -> formular -> application and the xml structure is flat, not hierarchically.
If I filter with such a if statement, I will get a position starting at 5 and not 1:
<xsl:for-each select="//lbDMF/formularfields/formular[#tablename=$FKTable]">
<xsl:variable name="DisplayField1" select="#fkname"/>
<xsl:variable name="FKFormularRefId" select="#formularid"/>
<xsl:if test="//lbDMF/formulare/formular[#ID=$FKFormularRefId][#applicationid=$ApplicationID]/#applicationid=$ApplicationID">
Foo.VisibleIndex = <xsl:value-of select="position()-1"/>;
My question is as follows: Can I move the inner if condition into the for-each select?
If, how could I do this (I tried and failed)?
If there is a different way to filter, would I affect the position?
I am using libxslt from my C++ application and not any java based xslt processor.
Thanks,
Lothar
In general, with cross-references you can works with keys and set up
<xsl:key name="table-name" match="lbDMF/formularfields/formular" use="#tablename"/>
that way the expression //lbDMF/formularfields/formular[#tablename=$FKTable] can be shortened to key('table-name', $FKTable).
Then you can set up a second key
<xsl:key name="id" match="lbDMF/formulare/formular" use="#ID"/>
and now you can write //lbDMF/formulare/formular[#ID=$FKFormularRefId] as key('id', $FKFormularRefId).
I am not sure why you have #applicationid=$ApplicationID both in a predicate expression and in the last step, that seems redundant.
So using the keys I think you can use
<xsl:for-each select="key('table-name', $FKTable)[key('id', #formularid)/#applicationid=$ApplicationID]">
Foo.VisibleIndex = <xsl:value-of select="position()-1"/>;
</xsl:for-each>

XPath sorting inline if statement

I've been trying to wrap my head around using XPath and XQuery for this with the help of some previous posts to no avail. Right now I have null child nodes which should just default to ordering at the end of a sort but unfortunately, the sort does not occur at all on these null nodes. As a result I have been trying to find a way to set them to zero during the sorting section. Here is a sample below:
<xsl:for-each select="MyItems/Item">
<xsl:sort select="Order/obj/Number" order="ascending">
I want to do something similar to an inline if statement as part of the sort like in C# below:
foreach(item in MyItems.OrderBy(Order/obj/Exists != false ? Order/obj/Number : 0)
I was using these links: dynamic xpath expression and XSLT transfom with inline if statements to try and understand but I'm still not getting it. Any help is appreciated. I need the solution in XSLT.
Your situation is unclear as you say nothing about the contents of your XML or the nature of your XSLT transform. But it sounds something like you have Item elements with no Order/obj/Number elements to sort on?
I would code that something like this
<xsl:template match="/root">
<xsl:copy>
<xsl:apply-templates select="MyItems/Item[Order/obj/Number]">
<xsl:sort select="Order/obj/Number" />
</xsl:apply-templatesh>
<xsl:apply-templates select="MyItems/Item[not(Order/obj/Number)]" />
</xsl:copy>
</xsl:template>
<xsl:template select="MyItems/Item">
<xsl:copy-of select="current()" />
</xsl:template>
Talking about "null nodes" isn't helpful. It's not a well-defined term. Show us your XML, your desired results and your actual results, and we can help you.
What should happen is that if the select expression in xsl:sort returns an empty sequence/node-set, the effective sort key is a zero-length string, so these items sort before any others (assuming ascending order).

Break the XSLT for-each loop when first match is found

I am having trouble to display the first matching value, like
<test>
<p>30</p>
<p>30{1{{23{45<p>
<p>23{34</p>
<p>30{1{98</p>
</test>
<test2>
<p1>text</p1>
</test2>
So i want to loop through the <test></test> and find the value of <p> node whose string length is greater than 2 and that contains 30. I want only the first value.
so i tired the following code
<xsl:variable name="var_test">
<xsl:for-each select="*/*/test()>
<xsl:if string-length(p/text())>2 and contains(p/text(),'30'))
<xsl:value-of select="xpath">
</xsl:variable>
the problem is the var_test is being null always.
if i try directly with out any variable
<xsl:for-each select="*/*/test()>
<xsl:if string-length(p/text())>2 and contains(p/text(),'30'))
<xsl:value-of select="xpath">
I am getting the following output
<p>30{1{23{4530{1{98</p>
but the desired output is
<p>0{1{23{45</p>
so how can i achieve this?
Instead of the for-each, use
<xsl:copy-of select="(*/*/test/p[string-length() > 2 and
contains(.,'30'))] )[1]" />
The [1] selects only the first matching <p>. (Updated: I changed the XPath above in response to #markusk's comment.)
The above will output that <p> element as well as its text content, as shown in your "desired output". If you actually want only the value of the <p>, that is, its text content, use <xsl:value-of> instead of <xsl:copy-of>.
Addendum:
The idea of breaking out of a loop does not apply to XSLT, because it is not a procedural language. In a <xsl:for-each> loop, the "first" instantiation (speaking in terms of document order, or sorted order) of the loop is not necessarily evaluated at a time chronologically before the "last" instantiation. They may be evaluated in any order, or in parallel, because they do not depend on each other. So trying to "break out of the loop", which is intended to cause "subsequent" instantiations of the loop not to be evaluated, cannot work: if it did, the outcome of later instantiations would be dependent on earlier instantiations, and parallel evaluation would be ruled out.