Nested XSL Increment Variable - xslt

I have the following code:
<xsl:for-each select"/*/cities/city">
<xsl:for-each select="./items/item">
<xsl:variable name="counter" select="position()" />
</xsl:for-each>
</xsl:for-each>
What i want to do is make a global counter that accounts for both the global scope and the inner scope. So, if for example:
The first iteration of the inner loop has 7 items, the count would be 7. When it performs the second iteration, the new count should be 7 + position().
How Do I accomplish something like that?

It's difficult to answer without seeing an input and the expected output. Apparently you could do simply:
<xsl:for-each select"/*/cities/city/items/item">
<xsl:variable name="counter" select="position()" />
</xsl:for-each>
to get the specified result. If for some reason you do need to nest the two xsl:for-each instructions, consider using xsl:number to number the items.

Related

XSLT Keep adding variable in a loop

I am looking for an idea on how to be able to add variable in for-each loop.
<group>
<spare bitCnt="5"/>
<integer name="A" bitCnt="11"/>
<spare bitCnt="15"/>
<integer name="B" bitCnt="1"/>
</group>
Expected header file output:
UINT16 A: 11;
UINT16 spare: 5;
UINT16 B: 1;
UINT16 spare 15;
I need to keep adding the bitCnt of variables in the 'group' and if the variable falls on the word boundary of 2 bytes, I need to swap the order of the elements.
My question is "how can I keep track of the bitCnt as I run the elements in the for-each loop?"
I would something like totalBitCnt in the xslt and in for-each loop, keep adding the totalBitCnt to determine if "totalBitCnt mod 16" is zero.
So, it would increment like 5, 18(5+13), 23(15+18), 24(23+1) as it runs the loop.
Any general approach is highly appreciated.
Thanks,
I'm not sure I grok you particular context so I'll answer in general terms.
Accumulating in a for loop is an iterative approach and XSLT (I guess you're using 1.0) is very limited in that matter. I think your best bet is to set up something recursive.
Named templates could be defined and invoked instead of the for-each loop. You'd define it with parameters such as: a list of element to process and the accumulator.
<xsl:template name="accumulator-template">
<xsl:param name="elements"/>
<xsl:param name="accumulator"/>
<!-- do stuff to the first element -->
<xsl:variable name="elements-left" select="$elements[???]"/> <!-- Exclude element that was processed -->
<xsl:choose>
<xsl:when test="$elements-left">
<!-- LOOPING -->
<xsl:call-template name="accumulator-template">
<xsl:with-param name="elements" select="$elements-left"/>
<xsl:with-param name="accumulator" select="$accumulator + number(#stuff)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- END OF LOOP -->
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Then you'd invoke it instead of writing an inline for-each loop:
<xsl:call-template name="accumulator-template">
<xsl:with-param name="elements" select="group"/>
<xsl:with-param name="elements" select="0"/> <!-- or any other starting value -->
</xsl:call-template>

xsl:key not working when looping through nodeset obtained with xalan:nodeset

I have found a case where xsl:key seems not to be working.
I am using XSLT 1 with Xalan (compiled) and this is what is happening:
1.- This works: key named test1 works fine:
<xsl:variable name="promosRTF">
<xsl:copy-of select="promo[#valid='true']"/>
</xsl:variable>
<xsl:variable name="promos" select="xalan:nodeset($promosRTF)" />
<!-- now the key: when defined and used here it works fine: -->
<xsl:key name="test1" match="//promo" use="'anytext'" />
<xsl:for-each select="key('test1','anytext')/*">
loop through elements in key... ok, works fine
</xsl:for-each>
<xsl:for-each select="$promos/*">
..loop through elements in variable $promos ...it is not empty so the loop iterates several times
</xsl:for-each>
2.- This doesn't work: key test1 is now defined and used (which is the important point, I guess) inside a loop that iterates through elements obtained with xalan:nodeset
<xsl:variable name="promosRTF">
<xsl:copy-of select="promo[#valid='true']"/>
</xsl:variable>
<xsl:variable name="promos" select="xalan:nodeset($promosRTF)" />
<xsl:for-each select="$promos/*">
<!-- now the key: when defined and used (or just used) inside this for-each loop it does nothing: -->
<xsl:key name="test1" match="//promo" use="'anytext'" />
<xsl:for-each select="key('test1','anytext')/*">
loop through elements in key... NOTHING HERE, it does not work :(
</xsl:for-each>
..loop through elements in variable $promos ...it is not empty so iterates several times
</xsl:for-each>
Does anybody know what is happening? Notice that variable $promos is not empty, so the loop really iterates, it is the key used inside it which does nothing.
Thank you very much in advance.
PS: after Martin's answer I post this alternative code, which doesn't work either:
<xsl:variable name="promosRTF">
<xsl:copy-of select="promo[#valid='true']"/>
</xsl:variable>
<xsl:variable name="promos" select="xalan:nodeset($promosRTF)" />
<!-- now the key: defined as a first level element: -->
<xsl:key name="test1" match="//promo" use="'anytext'" />
<xsl:for-each select="$promos/*">
<!-- but used inside this for-each loop it does nothing: -->
<xsl:for-each select="key('test1','anytext')/*">
loop through elements in key... NOTHING HERE, it does not work :(
</xsl:for-each>
..loop through elements in variable $promos ...it is not empty so iterates several times
</xsl:for-each>
Solution: In Martin's comments was the key to the problem: nodes in a result tree fragment are treated as nodes in a different document.
Martin pointed the workaround too: In XSLT 1.0 you need to change the context node using a for-each and then call key inside the for-each. This code will work then:
<xsl:variable name="promosRTF">
<xsl:copy-of select="promo[#valid='true']"/>
</xsl:variable>
<xsl:variable name="promos" select="xalan:nodeset($promosRTF)" />
<!-- now the key -->
<xsl:key name="test1" match="//promo" use="'anytext'" />
<xsl:for-each select="$promos/*">
<!-- inside this for-each we are in a different *document*, so we must go back: -->
<xsl:for-each select="/">
<xsl:for-each select="key('test1','anytext')/*">
loop through elements in key... and now IT WORKS, thanks Martin :)
</xsl:for-each>
</xsl:for-each>
..loop through elements in variable $promos ...it is not empty so iterates several times
</xsl:for-each>
I am surprised that you don't get an error by putting the xsl:key into the for-each. In http://www.w3.org/TR/xslt#key you can see that xsl:key is defined as a <!-- Category: top-level-element --> top level element so you need to put it as a child of xsl:stylesheet or xsl:transform.

XSLT 1.0: if statement when position matches a variable value

I'm trying to insert an IF statement into an existing FOR-EACH loop to do something slightly different if it matches a variable from another node (the node I want is actually a sibling of it's parent - if that makes sense!?).
The value is a simple integer. I basically want to say: If the position is equal to the variable number then do XXXX.
Here is the XSLT, it's only v1.0 and not 2.0 that I can use.
<xsl:for-each select="/Properties/Data/Datum[#ID='ID1']/DCR[#Type='accordion_tab']/accordion_tab/sections">
<h3 class="accordionButton">
<xsl:if test="position()='openpane value to go here'">
<xsl:attribute name="class">
<xsl:text>new text</xsl:text>
</xsl:attribute>
</xsl:if>
</xsl:for-each>
My XML extract is here:
<sections>
<title>title</title>
<text>some text</text>
</sections>
<openpane>2</openpane>
You didn't make this clear in your question, but I assume you iterate over the sections elements in your for-each loop. From the for-each loop you can reach the openpane element by going through the parent of the current sections element:
<xsl:for-each select="sections">
<xsl:if test="position() = ../openpane">
...
</xsl:if>
</xsl:for-each>
You could also define a variable referring to the openpane element first:
<xsl:variable name="openpane" select="openpane"/>
<xsl:for-each select="sections">
<xsl:if test="position() = $openpane">
...
</xsl:if>
</xsl:for-each>

XSLT for-each counter - how to access data

for performance testing purposes I want to take a small XML file and create a bigger one from it - using XSLT. Here I plan to take each entity (Campaign node in the example below) in the original XML and copy it n times, just changing its ID.
The only way I can think of to realize this, is a xsl:for-each select "1 to n". But when I do this I do not seem to be able to access the entity node anymore (xsl:for-each select="campaigns/campaign" does not work in my case). I am getting a processor error: "cannot be used here: the context item is an atomic value".
It seems that by using the "1 to n" loop, I am loosing the access to my actual entity. Is there any XPath expression that gets me access back or does anyone have a completely different idea how to realize this?
Here is what I do:
Original XML
<campaigns>
<campaign id="1" name="test">
<campaign id="2" name="another name">
</cmpaigns>
XSLT I try to use
<xsl:template match="/">
<xsl:element name="campaigns">
<xsl:for-each select="1 to 10">
<xsl:for-each select="campaigns/campaign">
<xsl:element name="campaign">
<xsl:copy-of select="#*[local-name() != 'id']" />
<xsl:attribute name="id"><xsl:value-of select="#id" /></xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:for-each>
</xsl:element>
</xsl:template>
Define a variable as the first thing in the match, like so:
<xsl:variable name="foo" select="."/>
This defines a variable $foo of type nodeset. Then access it like this
<xsl:for-each select="$foo/campaigns/campaign">
...
</xsl:for-each>

How to increment a variable inside an XSLT template?

here is briefly the problem I'm facing:
I have built a custom Content Query webpart, I fill this CQWP with items from a list. What I would like to do is to have a special separator each 3 items.
How to tell the XSLT that the current item is the 3, 6 or 9th one, and therefore, that a separator has to be put ?
what I have thought of would be to do something like that in the itemstyle.xsl:
<xsl:variable name="increment" select="0"/>
<xsl:template>
<xsl:with-variable name="increment" select="$increment+1"/>
<xsl:if increment = multiple de 3>
-put a separator-
</xsl:if>
</xsl:template>
but it appears that global variable cannot be used this way. Therefore my second idea would to get sortof the "row number" of the corresponding item in order to get the same information.
Does anybody have any idea of how I can solve this problem ?
XSLT does not do destructive update. Therefore you cannot assign a value to a variable and then overwrite the variable with a new value later.
In your case, you can use the position() function with modulus arithmetic:
<xsl:for-each select="*">
<!-- normal processing here -->
<xsl:if test="position() mod 3 = 0">
<!-- separator here -->
</xsl:if>
</xsl:for-each>