How to increment a variable inside an XSLT template? - xslt

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>

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.

Conditional position-check in XSLT

I have an XSLT file which renders articles. I'm passing a variable into the XSLT file which is supposed to limit the number of records output. The trouble is, this 'limit' variable is optional - if it's not there the XSLT file needs to output all values; if the 'limit' variable is a number, then it needs to only output that number of items (presumably using a position() test).
Here's my code:
<xsl:variable name="limit" select="/macro/limit"/>
<xsl:template match="/">
<xsl:variable name="allNodes" select="$localSiteRoot/descendant-or-self::*[articles != '']"/>
<xsl:for-each select="$allNodes">
<div class="articleItem">
<h3><xsl:value-of select="./articleHeader"/></h3>
<xsl:if test="./articleSubheader != ''">
<h4><xsl:value-of select="./articleSubheader"/></h4>
</xsl:if>
</div>
</xsl:for-each>
</xsl:template>
Clearly I could just do an XSLT choose around this and say
<xsl:choose>
<xsl:when select="$limit!= ''">
<xsl:if test="position() < $limit">
But then I'd need to repeat the code twice. There must be a better way, perhaps using templates, but I just can't figure out how it would work and my XSLT isn't great.
Could anyone point me in the right direction with the best/neatest way to approach this?
Thanks!
Just use:
<xsl:for-each select="$allNodes[not(position() > $limit)]">
Explanation:
If the value of $limit is castable to a number, then the expression in the select attribute above selects not more than $limit nodes.
If the value of $limits isn't castable to a number (e.g. empty or 'abc') then it is converted to NaN. By definition comparisons involving NaN are always false(), therefore the predicate not(position() > $limit is true() and in this case all nodes in $allNodes are selected.

I need to build a string from a list of attributes (not elements) in XSLT

want to make a comma-delimited string from a list of 3 possible attributes of an element.
I have found a thread here:
XSLT concat string, remove last comma
that describes how to build a comma-delimited string from elements. I want to do the same thing with a list of attributes.
From the following element:
<myElement attr1="Don't report this one" attr2="value1" attr3="value2" attr4="value3" />
I would like to produce a string that reads: "value1,value2,value3"
One other caveat: attr2 thru attr4 may or may not have values but, if they do have values, they will go in order. So, attr4 will not have a value if attr3 does not. attr3 will not have a value if attr2 does not. So, for an attribute to have a value, the one before it in the attribute list must have a value.
How can I modify the code in the solution to the thread linked to above so that it is attribute-centric instead of element-centric?
Thanks in advance for whatever help you can provide.
This is easy in principe, but only if it is really clear which attribute you want to exclude. Since attributes are not by definition ordered in XML (in contrast the elements), you need to say how the attribute(s) to skip can be identified.
Edit: Regarding the attribute order, XML section 3.1 says:
Note that the order of attribute specifications in a start-tag or empty-element tag is not significant.
That said, something like this should do the trick (adjust the [] condition as you see fit):
<xsl:template match="myElement">
<xsl:for-each select="#*[position()!=1]">
<xsl:value-of select="." />
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:for-each>
</xsl:template>
In XSLT 2.0 it is as easy as
<xsl:template match="myElement">
<xsl:value-of select="#* except #att1" separator=","/>
</xsl:template>
I would appreciate Lucero's answer .. He definitely has nailed it ..
Well, Here is one more code which truncates attribute attr1, which appears at any positions other than 1 in attribute list.
scenario like this::
<myElement attr2="value1" attr3="value2" attr4="value3" attr1="Don't report this one" />
Here is the XSLT code .. ::
<xsl:template match="myElement">
<xsl:for-each select="#*[name()!='attr1']">
<xsl:value-of select="." />
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:for-each>
</xsl:template>
The output will be:
value1,value2,value3
If you wish to omit more than one attribute say .. attr1 and attr2 ..
<xsl:template match="myElement">
<xsl:for-each select="#*[name()!='attr1' and name()!='attr2']">
<xsl:value-of select="." />
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:for-each>
</xsl:template>
The corresponding output will be:
value2,value3

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>