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

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.

Related

XSLT 1.0 - change the order of lines

I want to change the order of line cac:InvoiceLine depending on this node:
cac:AdditionalItemProperty/cbc:Value
All InvoiceLines that have Item type=RC must be gruop at the end of lines, and all that have CU must be on the top.
If the mentioned values are the only ones you are concerned about, then it seems like you could just sort alphabetically by that value; see xsl:sort. You could just put this inside the xsl:for-each or xsl:apply-templates where you process your invoice lines:
<xsl:sort select="cac:AdditionalItemProperty/cbc:Value" />
On the other hand, if you only want to output only the line items with the mentioned values, you could select them separately. For example, assuming you have a template which matches your invoice lines, you'd first apply it to the 'CU' ones and then to the 'RC' ones:
<xsl:apply-templates select="cac:InvoiceLine[cac:AdditionalItemProperty/cbc:Value='CU']" />
<xsl:apply-templates select="cac:InvoiceLine[cac:AdditionalItemProperty/cbc:Value='RC']" />

XSLT: Testing position against concurrent criteria within xsl:for-each

Consider the following two xsl:value-of statements using XPath expressions testing the position of a node.
(1) This one, as I understand it, will return the value of the /ChildThree child of a /Parent where three things are independently true of that /Parent - (i) it has a /ChildOne with value "x"; AND (ii) it has a /ChildTwo with value "y"; AND (iii) it is the second or subsequent /Parent within its immediate ancestor:
<xsl:value-of select="Parent[ChildOne='x'][ChildTwo='y'][position()>=2]/ChildThree"/>
(2) This one, by contrast, will return the value of the /ChildThree child of a /Parent where that /Parent is the second or subsequent /Parent OF THOSE THAT HAVE BOTH a /ChildOne with value "x" AND a /ChildTwo with value "y":
<xsl:value-of select="(Parent[ChildOne='x'][ChildTwo='y'])[position()>=2]/ChildThree"/>
So far so good. However, what happens in the following example? Here, I'm trying to get the value of the /ChildThree child of any /Parent for which the first two criteria are concurrently true, but only precede it with a space if the /Parent is the second or subsequent within that subset (i.e., like example (2), above). How can I dictate how the position criterion within the xsl:if statement applies to the criteria in the xsl:for-if statement, when it is not part of the same XPath expression?
<xsl:for-each select="Parent[ChildOne='x'][ChildTwo='y']">
<xsl:if test="position()>=2">
<xsl:text> </xsl:text>
</xsl:if>
<xsl:value-of select="ChildThree"/>
</xsl:for-each>
The two statements (1) and (2) are actually equivalent, and will return the same results.
position() is context-sensitive. It will give you the position of the current node within the set of nodes previously selected. So, when you have the expression Parent[ChildOne='x'][ChildTwo='y'] it is returning a set of nodes where two conditions is true. Doing Parent[ChildOne='x'][ChildTwo='y'][position()>=2] will give the second (or greater) node in this list.
The way you are describing how you think (1) works, would actually look like this.
<xsl:value-of select="Parent[position()>=2][ChildOne='x'][ChildTwo='y']/ChildThree"/>
One way to think of it, is to think of every condition in square brackets filtering what has come before.
Try it out on this XML, for example
<Parents>
<Parent>
<ChildOne>a</ChildOne>
<ChildTwo>b</ChildTwo>
<ChildThree>c</ChildThree>
</Parent>
<Parent>
<ChildOne>x</ChildOne>
<ChildTwo>y</ChildTwo>
<ChildThree>z1</ChildThree>
</Parent>
<Parent>
<ChildOne>x</ChildOne>
<ChildTwo>y</ChildTwo>
<ChildThree>z2</ChildThree>
</Parent>
</Parents>
Your statements (1) and (2) both return z2, but doing Parent[position()>=2][ChildOne='x'][ChildTwo='y']/ChildThree returns z1.
What this means is that your xsl:for-each should actually give you the results you expect.

Debug possible escaped-text issue in XSLT?

I have an XSL template that gives different results when used in two different contexts.
The template manifesting the defect is:
<xsl:template match="*" mode="blah">
<!-- snip irrelevant stuff -->
<xsl:if test="see">
<xsl:message>Contains a cross-ref. <xsl:value-of select="."/></xsl:message>
</xsl:if>
<xsl:apply-templates select="."/>
</xsl:template>
Given:
<el>This is a '<see cref="foo"/>' cross-referenced element.</el>
In one situation, I get the desired result:
Contains a cross-ref. This is a ' ' cross-referenced element.
(the <see/> is being dealt with as an XML element and is ultimately matched by another template.)
But in another situation, the xsl:if doesn't trigger and if I output the contents with <xsl:message><xsl:value-of select="."/>, I get:
This is a '<see cref="foo"/>' cross-referenced element.
It seems to me that in the latter improperly-behaving scenario, it's acting like it's been output-escaped. Does that make sense? Am I barking up the wrong tree? This is a typically complex XSL situation and trying to trace the call-stack is difficult; is there a particular XSLT processing command I should be looking for?

XSL unique value key

Goal
(XSLT 1.0). My goal is to take a set of elements, S, and produce another set, T, where T contains the unique elements in S. And to do so as efficiently as possible. (Note: I don't have to create a variable containing the set, or anything like that. I just need to loop over the elements that are unique).
Example Input and Key
<!-- My actual input consists of a bunch of <Result> elements -->
<AllMyResults>
<Result>
<someElement>value</state>
<otherElement>value 2</state>
<subject>Get unique subjects!</state>
</Result>
</AllMyResults>
<xsl:key name="SubjectKey" match="AllMyResults/Result" use="subject"/>
I think the above works, but when I go to use my key, it is incredibly slow. Below is the code for how I use my key.
<xsl:for-each select="Result[count(. | key('SubjectKey', subject)[1]) = 1]">
<xsl:sort select="subject" />
<!-- Do something with the unique subject value -->
<xsl:value-of select="subject" />
</xsl:for-each>
Additional Info
I believe I am doing this wrong because it slowed down my XSL considerably. As some additional info, the code shown above is in a separate XSL file from my main XSL file. From the main XSL, I am calling a template that contains the xsl:key and the for-each shown above. The input to this template is an xsl:param containing my node-set (similar to the example input shown above).
I can't see any reason from the information given why the code should be slow. It might be worth seeing if the slowness is something that happens on all XSLT processors, or if it's peculiar to one.
Try substituting
count(. | key('SubjectKey', subject)[1]) = 1
with:
generate-id() = generate-id(key('SubjectKey', subject)[1])
In some XSLT processors the latter is much faster.

How-to break a for-each loop in 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>