Obtain position() from “for” expression in Xpath 2
One misunderstood and under utilized feature of XPath 2 is the for expression. The principal is fairly simple, whenever you have a sequence to iterate through, the “for” expression provides a simple mechanism to extract each of the sequences’ item() to a context variable and return something each time it changes context.
For those familiar with XQuery FLOWR expression, the “for” expression is very similar, but offers less features, as “let”, “order by” and “where” aren’t included in XPath 2.
One other feature of XQuery’s FLOWR expression, which is also missing from XPath 2, and I am providing a solution for in this blog is the “position” variable which can be useful at times, when you need to ad conditional statements to your return value, depending on the position of the item() in context.
Let’s first have a look at how you’d use a “for” expression in XSL.
<xsl:value-of select="for $i in (1 to 9) return $i"/>
returns: 1 2 3 4 5 6 7 8 9
Lets now use a pre-defined sequence of xs:string items instead, stored in a variable, and comma separate the items, but for the 2nd item where we also want to append the character “*”. We also want to separate the penultimate and the last item with an “and”, rather than a comma.
Because the “for” expression doesn’t provide any mechanism to keep track of context position, there isn’t a straight forward way to achieve the logic described above within the expression itself. Instead we’ll iterate the item count range, by creating a dynamic sequence based on the sequences’ item count.
<xsl:variable name="family" select="('Miguel','Louise','Merlyn','Kai','Eden')" as="xs:string*"/>
<xsl:value-of select="
for $i in (1 to count($family)) return
concat(
$family[$i],
if ($i = 2) then '*' else (),
if ($i = ( count($family) - 1)) then ' and'
else if ($i < (count($family) - 1)) then ','
else ''
)
"/>
Returns: Miguel, Louise*, Merlyn, Kai and Eden
Traditionally a “for” expression in XPath 2 would directly iterate through the sequence you’re planning to extract values from, but in this example, you’ll notice the “for” expression is iterating through a number sequence, consisting of the numbers 1 to the item count of the sequence held by the vatiable $family. ((1 to count($family)))
It is then possible to access each item of the $family variable, by dynamically providing the position of the item in the $family sequence, based on context. ($family[$i]).
You can then add various conditional statements to your return value, based on the value of $I which holds the context position, rather than the value you’re planning to output. ( if ($i = 2) then '*' else ())
The above example could be also achieved with an xsl:for-each which provides access to the position() function based on context.
<xsl:for-each select="$family">
<xsl:value-of select="."/>
<xsl:value-of select="
if (position() = (last() - 1)) then ' and '
else if(not(position() = last())) then ', '
else ''
"/>
</xsl:for-each>
8 comments:
I am fairly new to xslt. Any pointers to similar samples would help as well. Can you please help me achieve the below:
I have a source xml as below:
100520
20
100625
10
100710
1
I need to create a target xml as below:
20
100520
10
100625
1
100710
can't see your xml dude, you'll need to escape < to <
I would like to exchange links with your site xsltbyexample.blogspot.com
Is this possible?
what link ?
Hi there,
This is a inquiry for the webmaster/admin here at xsltbyexample.blogspot.com.
May I use some of the information from your post right above if I give a link back to your website?
Thanks,
Peter
please do, would be nice if you kept me informed about the context though.
thanks
Miguel
Thank you, thank you, thank you, thank you!
Have spent all day searching the web for a snippet of XSL that will allow me to output a set of elements from a sequence of strings, without resorting to recursion, and this is it!
Excellent, many thanks for posting this.
JGA comments like that are what keeps me motivated to continue adding recipes to these blogs. Glad you found good use to this one, and thank you for being appreciative.
Post a Comment