variable inside second for loop - xslt

I am trying to print variable value in second for loop of xslt but not getting displayed
here is my code
<xsl:for-each select="CampaignData/Mailer/CustomerInfo/CustInfFld/Coupons/Cust_Sel_Coupons">
<xsl:sort select="#CouponPosition"/>
<xsl:variable name="CouponID">
<xsl:value-of select="#CouponID"/>
</xsl:variable>
<xsl:for-each select="CampaignData/Mailer/Coupons/Cntr_Sel_Coupons">
<div style='color:red;'><xsl:value-of select='$CouponID'/></div>
</xsl:for-each>
</xsl:for-each>

I think one potential problem is that the outer for-each changes the context node so the inner for-each needs to take that into account:
<xsl:for-each select="CampaignData/Mailer/CustomerInfo/CustInfFld/Coupons/Cust_Sel_Coupons">
<xsl:sort select="#CouponPosition"/>
<xsl:variable name="CouponID" select="#CouponID"/>
<xsl:for-each select="ancestor::CampaignData/Mailer/Coupons/Cntr_Sel_Coupons">
<div style='color:red;'><xsl:value-of select='$CouponID'/></div>
</xsl:for-each>
</xsl:for-each>
I have also changed to code for the variable to simply write the expression you want to select in the select attribute instead of nesting a value-of.
If you still have problems then post a sample of the XML you want to process.

The select attribute of the nested xsl:for-each doesn't select anything and this is why its body isn't executed even once.
Inside the inner xsl:for-each your current node is of the form:
CampaignData/Mailer/CustomerInfo/CustInfFld/Coupons/Cust_Sel_Coupons
The select attribute of the inner xsl:for-each specifies:
CampaignData/Mailer/Coupons/Cntr_Sel_Coupons
this is a relative XPath expression and it is resolved off the current node. Thus, the specified select for the inner xsl:for-each is actually (from outside of the outer xsl:for-each):
CampaignData/Mailer/CustomerInfo/CustInfFld/Coupons/Cust_Sel_Coupons
/CampaignData/Mailer/Coupons/Cntr_Sel_Coupons
It is highly unlikely there are any such nodes in the source XML document that you haven't shown.
Without having the source XML it isn't possible to say what exactly the relative expression should be -- most likely:
Change:
<xsl:for-each select=
"CampaignData/Mailer/Coupons/Cntr_Sel_Coupons">
to:
<xsl:for-each select=
"../../../../Coupons/Cntr_Sel_Coupons">

Related

Nested XSL Increment Variable

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.

check if repeating node is empty in xslt

I have xml like defined below . The node EducationDetails can repeat (unbounded).
<PersonalDetailsResponse>
<FirstName></FirstName>
<LastName></LastName>
<EducationDetails>
<Degree></Degree>
<Institution></Institution>
<Year></Year>
</EducationDetails>
<EducationDetails>
<Degree></Degree>
<Institution></Institution>
<Year></Year>
</EducationDetails>
</PersonalDetailsResponse>
I want to create another xml from the above one using xslt.
My requirement is, if there is no data in any of the EducationDetails child nodes , then the resulting xml has to get data from another source.
My problem is , I am not able to check if all the EducationDetails child nodes are empty.
Since variable value cannot be changed in xslt , I tried using saxon with below code.
xmlns:saxon="http://saxon.sf.net/" extension-element-prefixes="saxon"
<xsl:variable name="emptyNode" saxon:assignable="yes" select="0" />
<xsl:when test="count(ss:education) > 0">
<xsl:for-each select="ss:education">
<xsl:if test="not(*[.=''])">
<saxon:assign name="emptyNode">
<xsl:value-of select="1" />
</saxon:assign>
</xsl:if>
</xsl:for-each>
<xsl:if test="$emptyNode = 0">
<!-- Do logic if all educationdetails node is empty-->
</xsl:if>
</xsl:when>
But it throwing exception "net.sf.saxon.trans.XPathException: Unknown extension instruction " .
It looks like saxon 9 jar is required for it ,which I am not able to get from my repository.
Is there a simpler way to check if all the child nodes of are empty.
By empty I mean, child nodes might be present, but no value in them.
Well, if you use <xsl:template match="PersonalDetailsResponse[EducationDetails[*[normalize-space()]]">...</xsl:template> then you match only on PersonalDetailsResponse element having at least one EducationDetails element having at least one child element with non whitespace data. As you seem to use an XSLT 2.0 processor you can also use the perhaps clearer <xsl:template match="PersonalDetailsResponse[some $ed in EducationDetails/* satisfies normalize-space($ed)]">...</xsl:template>.
Or perhaps if you want a variable inside the template use
<xsl:template match="PersonalDetailsResponse">
<xsl:variable name="empty-details" select="not(EducationDetails/*[normalize-space()])"/>
<xsl:if test="$empty-details">...</xsl:if>
</xsl:template>
With XSLT 2.0 the use of some or every satisfies might be easier to understand e.g.
<xsl:template match="PersonalDetailsResponse">
<xsl:variable name="empty-details" select="every $dt in EducationDetails/* satisfies not(normalize-space($dt))"/>
<xsl:if test="$empty-details">...</xsl:if>
</xsl:template>
But usually writing templates with appropriate match conditions saves you from using xsl:if or xsl:choose inside of a template.

How to break XSLT for-each loop?

I know break is not possible in XSLT for-each loop.
But from following example, If I want to show 'something' element exist or not. Then I will go through loop and once I got it I want to break loop.
I will make one bool variable say isExist=false;
I will go through all nodes one by one.
Once I got 'something', I will change its value to isExist=true
want to break loop here.
Now I want to break loop bcoz I want to show status as true or false. But If I will not use break after step 3, it will go to loop and make change it to false and I will get status as false.
My question is: If break not possible then How to achieve this result?
I am following this link How to interrupt an XSLT for-each loop for not contiguos elements?
According to this link I will get first two occurrence but it doen't say 'something' exist or not.
<root>
<item/>
<item/>
<something/>
<item/>
</root>
If all you're after is whether <something/> exists or not, you could also just check using
<xsl:if test="/root/something">
// do fancy stuff...
</xsl:if>
That's not how variables work in XSLT - variables are always lexically scoped to their containing element, and once set they cannot be changed.
You need to learn to think more declaratively rather than procedurally - tell the processor what you want it to find rather than how you would look for it. The XPath expression boolean(/root/something) will give you what you want - true if there is a something element as a child of the root and false otherwise.
When you write a XSLT template that matches an element selector, for example:
<xsl:template match="entry"> .... </entry>
and you have a XML file like this one:
<entries>
<entry number="345" type="A"/>
<entry number="123" type="B"/>
<entry number="334" type="A"/>
<entry number="322" type="C"/>
</entries>
The XSLT template will be processed for all entry elements. So most of the time, you never need to use a for-each in XSLT. In the above example, the template will be processed 4 times, since there are 4 entry elements to match. Inside each one you can extract or print the data that's available in that context (such as the attributes, in this example).
You can usually also perform many conditional expressions without ever using an <xsl:if> or <xsl:choose>, by restricting what you need via XPath predicates.
If you add another template for the entry elements with a predicate like this:
<xsl:template match="entry[#type='A']"> .... </entry>
then it will override the entry templates for the entry elements that have a type attribute with the value A. The others will be processed by the template that has no predicate.
Giving preference to these tools, instead of for-each, if and choose, will help you think in a more functional oriented way and understand how to use XSLT better.
Since variables and params can't be changed, you can't use a regular for-loop to increment or decrement it, but in case you need one, it is possible to write a for loop that increments a variable in XSLT 1.0, but it has to be done recursively (using xsl:call-template). In this strategy, you don't assign a different value to a variable, but perform an operation inside a variable block which will set the final value for that variable.
See, for example, this stylesheet which calculates a factorial of a number. The param n in each recursion has a final value which doesn't change but is used as the data for another n param in the next recursion:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:param name="number">5</xsl:param>
<xsl:template match="/">
<xsl:variable name="fac">
<xsl:call-template name="factorial">
<xsl:with-param name="n" select="$number"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$fac"/>
</xsl:template>
<xsl:template name="factorial">
<xsl:param name="n"/>
<xsl:choose>
<xsl:when test="$n > 1">
<xsl:call-template name="factorial">
<xsl:with-param name="n-1"/>
</xsl:call-template>
<xsl:value-of select="$n * $n - 1"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="1"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

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>

XSL: How best to store a node in a variable and then use it in future xpath expressions?

I need to be able to store a node set in variable and then perform more filting/sorting on it afterward. All the examples I've seen of this involve either using XSL2 or extensions neither of which are really an option.
I've a list of hotels in my XML doc that can be sorted/filtered and then paged through 5 at a time. I'm finding though I'm repeating alot of the logic as currently I've not found a good way to store node-sets in xsl variable and then use xpath on them for further filtering/sorting.
This is the sort of thing I'm after (excuse the code written of the top of my head so might not be 100%):
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="3_star_or_less" select="/results/hotels[number(rating) <= 3]" />
<xsl:for-each select="3_star_or_less">
<xsl:sort select="rating" />
</xsl:for-each>
Has anyone got an example of how best to do this sort of thing?
Try this example:
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="three_star_or_less"
select="$hotels[number(rating) <= 3]" />
<xsl:for-each select="$three_star_or_less">
<xsl:sort select="rating" />
<xsl:value-of select="rating" />
</xsl:for-each>
There is no problem storing a node-set in a variable in XSLT 1.0, and no extensions are needed. If you just use an XPath expression in select attribute of xsl:variable, you'll end up doing just that.
The problem is only when you want to store the nodes that you yourself had generated in a variable, and even then only if you want to query over them later. The problem here is that nodes you output don't have type "node-set" - instead, they're what is called a "result tree fragment". You can store that to a variable, and you can use that variable to insert the fragment into output (or another variable) later on, but you cannot use XPath to query over it. That's when you need either EXSLT node-set() function (which converts a result tree fragment to a node-set), or XSLT 2.0 (in which there are no result tree fragments, only sequences of nodes, regardless of where they come from).
For your example as given, this doesn't seem to be a problem. Rubens' answer gives the exact syntax.
Another note, if you want to be able to use the variable as part of an XPath statement, you need to select into the variable with <xsl:copy-of select="."/> instead of <xsl:value-of select="."/>
value-of will only take the text of the node and you wont be able to use the node-set function to return anything meaningful.
<xsl:variable name="myStringVar">
<xsl:value-of select="."/>
</xsl:variable>
<!-- This won't work: -->
<Output>
<xsl:value-of select="node-set($myStringVar)/SubNode" />
</Output>
<xsl:variable name="myNodeSetVar">
<xsl:copy-of select="."/>
</xsl:variable>
<!-- This will work: -->
<Output>
<xsl:value-of select="node-set($myNodeSetVar)/SubNode" />
</Output>