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

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>

Related

XSLT static key declaration using sequence constructor instead of #use?

XSLT2.0 seems to allow declaring key inline, inside the <key> element.
All the examples I have seen declare an intermediate XML fragment and match on that, using #use. I think that is wasteful.
Can you please provide an example of a XSLT 2.0 key declaration using sequence constructor inside the key element rather than #use?
Usually the value that you want to index is a very simple function of the objects being indexed, so the #use attribute works perfectly well. You can use a contained sequence constructor for more complex cases if you need to, but I've very rarely seen it needed. For example you might want to index sections by their section number like this:
<xsl:key name="k" match="section">
<xsl:number level="multi" count="section" format="1.1.1"/>
</xsl:key>
I don't know what makes you think that using the #use attribute is "wasteful".
I don't think I have used that feature so far and I can't think of a good sample for an obvious use case but let's assume foo elements have some value child elements and we want to sort the value elements and only key on the first or last few in sort order so we could use e.g.
<xsl:key name="by-first-three-values" match="foo">
<xsl:for-each select="value/xs:decimal(.)">
<xsl:sort select="."/>
<xsl:if test="position() lt 4">
<xsl:sequence select="."/>
</xsl:if>
</xsl:for-each>
</xsl:key>
Of course you could avoid that use by writing a function that sorts with perform-sort and then call that function in use="mf:sort(value)[position() lt 4]" but I guess there is at least the flexibility to do it inline of the xsl:key.
What I am after is even more simple, something similar to:
<xsl:key name="AcronymKey" match="a:acronymItem" use="a:acronym"/>
<xsl:template name="AcronymnStandsFor">
<xsl:param name="acronym"/>
<!-- change context to current document so the key will work -->
<xsl:for-each select="document('')">
<xsl:value-of select="key('AcronymKey',$acronym)/a:standsFor"/>
</xsl:for-each>
</xsl:template>
<a:acronymList>
<a:acronymItem>
<a:acronym>Ant</a:acronym>
<a:standsFor>Another Neat Tool</a:standsFor>
</a:acronymItem>
</a:acronymList>
But where the actual key is inside the key element. Is that possible, given the syntax?

Constructing, not selecting, XSL node set variable

I wish to construct an XSL node set variable using a contained for-each loop. It is important that the constructed node set is the original (a selected) node set, not a copy.
Here is a much simplified version of my problem (which could of course be solved with a select, but that's not the point of the question). I've used the <name> node to test that the constructed node set variable is in fact in the original tree and not a copy.
XSL version 1.0, processor is msxsl.
Non-working XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:variable name="entries">
<xsl:for-each select="//entry">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="entryNodes" select="msxsl:node-set($entries)"/>
<xsl:for-each select="$entryNodes">
<xsl:value-of select="/root/name"/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
XML input:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<name>X</name>
<entry>1</entry>
<entry>2</entry>
</root>
Wanted output:
X1X2
Actual output:
12
Of course the (or a) problem is the copy-of, but I can't work out a way around this.
There isn't a "way around it" in XSLT 1.0 - it's exactly how this is supposed to work. When you have a variable that is declared with content rather than with a select then that content is a result tree fragment consisting of newly-created nodes (even if those nodes are a copy of nodes from the original tree). If you want to refer to the original nodes attached to the original tree then you must declare the variable using select. A better question would be to detail the actual problem and ask how you could write a suitable select expression to find the nodes you want without needing to use for-each - most uses of xsl:if or xsl:choose can be replaced with suitably constructed predicates, maybe involving judicious use of xsl:key, etc.
In XSLT 2.0 it's much more flexible. There's no distinction between node sets and result tree fragments, and the content of an xsl:variable is treated as a generic "sequence constructor" which can give you new nodes if you construct or copy them:
<xsl:variable name="example" as="node()*">
<xsl:copy-of select="//entry" />
</xsl:variable>
or the original nodes if you use xsl:sequence:
<xsl:variable name="example" as="node()*">
<xsl:sequence select="//entry" />
</xsl:variable>
I wish to construct an XSL node set variable using a contained
for-each loop.
I have no idea what that means.
It is important that the constructed node set is the original (a
selected) node set, not a copy.
This part I think I understand a little better. It seems you need to replace:
<xsl:variable name="entries">
<xsl:for-each select="//entry">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
with:
<xsl:variable name="entries" select="//entry"/>
or, preferably:
<xsl:variable name="entries" select="root/entry"/>
The resulting variable is a node-set of the original entry nodes, so you can do simply:
<xsl:for-each select="$entries">
<xsl:value-of select="/root/name"/>
<xsl:value-of select="."/>
</xsl:for-each>
to get your expected result.
Of course, you could do the same thing by operating directly on the original nodes, in their original context - without requiring the variable.
In response to the comments you've made:
We obviously need a better example here, but I think I am getting a vague idea of where you want to go with this. But there are a few things you must understand first:
1.
In order to construct a variable which contains a node-set of nodes in their original context, you must use select. This does not place any limits whatsoever on what you can select. You can do your selection all at once, or in stages, or even in a loop (here I mean a real loop). You can combine the intermediate selections you have made in any way sets can be combined: union, intersection, or difference. But you must use select in all these steps, otherwise you will end up with a set of new nodes, no longer having the context they did in the source tree.
IOW, the only difference between using copy and select is that the former creates new nodes, which is precisely what you wish to avoid.
2.
xsl:for-each is not a loop. It has no hierarchy or chronology. All the nodes are processed in parallel, and there is no way to use the result of previous iteration in the current one - because no iteration is "previous" to another.
If you try to use xsl:for-each in order to add each of n processed nodes to a pre-existing node-set, you will end up with n results, each containing the pre-existing node-set joined with one of the processed nodes.
3.
I think you'll find the XPath language is quite powerful, and allows you to select the nodes you want without having to go through the complicated loops you hint at.
It might help if you showed us a problem that can't be trivially solved in XSLT 1.0. You can't solve your problem the way you are asking for: there is no equivalent of xsl:sequence in XSLT 1.0. But the problem you have shown us can be solved without such a construct. So please explain why you need what you are asking for.

Creating a variable number of nodes on target document without corresponding data on source document

I'm trying to map two documents witht the BizTalk Mapper and my target document should look like this:
<root>
<complexType>
<property>example</property>
</complexType>
<filler>
<padding>9999999</padding>
</filler>
<filler>
<padding>9999999</padding>
</filler>
<filler>
<padding>9999999</padding>
</filler>
</root>
The number of <filler> nodes that I should create is variable (from 0 to 9). It is basically the result of a calculation (based on some data provided in the source document).
Is there a way to create those <filler> nodes with some combination of functoids?
I tried to use the Table Looping functoid (created a table with only one column, the padding char '9') but doesn't really work because it creates as many <filler> nodes as rows are defined in the table, which is not what I want since the number of rows would have to be variable (again, based on a calculation).
What I currently do is pass the message (XmlDocument) to a C# method and then I programmatically append the <filler> nodes.
I'm hoping that there is a more "BizTalk-y" way of doing this with the Mapper.
I suspect that you will have to solve this problem by altering the XSLT.
Add some logic to create as many filler nodes as the result of your calculation dictates - you could create a template which you call in a loop perhaps, which would append a new filler section.
Hope this points you in the right direction.
As pointed out, XSLT can create nodes on the target document at will (I didn't know this and this was the key part).
Turns out that what I needed is a simple for-loop in XSLT. Once I realized this, a quick Google search yielded the following results:
http://quomon.com/question-How-to-make-a-for-loop-in-xslt-not-for-each-809.aspx
http://snippets.dzone.com/posts/show/930
Another thing worth noting is that (as pointed out by the first link), XSLT is a functional language, not procedural, so sometimes you do have to resort to using recursion or an extension.
This case is definitely one of those times since I couldn't use a careful selection of nodes using the select attribute on an xsl:for-each (since this filler data wasn't part of the source document).
Specifically, for this case, what I did was:
Add a Scripting functoid.
Add two inputs:
A constant with value "1" (this is the initial value of the i variable)
The length of the loop (number of times to repeat the body of the loop)
Paste the following XSLT template as an "Inline XSLT Call Template" script:
<xsl:template name="ForLoop">
<xsl:param name="i" /> <!-- index counter, 1-based, will be incremented with every recursive call -->
<xsl:param name="length" /> <!-- exit loop when i >= length -->
<!-- Output the desired node(s) if we're still looping -->
<!-- The base case is when i > length (in that case, do nothing) -->
<xsl:if test="$i <= $length">
<Filler>
<Padding>999999</Padding>
</Filler>
</xsl:if>
<!-- Call the ForLoop template recursively, incrementing i -->
<xsl:if test="$i <= $length">
<xsl:call-template name="ForLoop">
<xsl:with-param name="i">
<xsl:value-of select="$i + 1"/>
</xsl:with-param>
<xsl:with-param name="length">
<xsl:value-of select="$length"/>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>

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>