I would like to use XSL 2.0 (saxon9he.jar) to split data into groups by position. In this sample, I try to split market products into bags with 4 items in each bag. My testing indicates that position() is in the scope of the parent. Such that potato is position 2 as a child of the vegetable department, rather than position 5 in my selection of products. I would like to base the groups on the position within the selection, not the position within the parent.
XML Dataset:
<market>
<department name="fruit">
<product>apple</product>
<product>banana</product>
<product>grape</product>
</department>
<department name="vegetable">
<product>carrot</product>
<product>potato</product>
<product>squash</product>
</department>
<department name="paper">
<product>plates</product>
<product>napkins</product>
<product>cups</product>
</department>
<department name="cloths">
<product>shirts</product>
<product>shorts</product>
<product>socks</product>
</department>
</market>
XSL Template:
<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" exclude-result-prefixes="xs fn">
<xsl:output indent="no" method="text"/>
<!-- place 4 items in each bag -->
<xsl:template match="/">
<xsl:for-each-group select="/market/department/product"
group-ending-with="/market/department/product[position() mod 4 = 0]">
<xsl:variable name="file"
select="concat('bags/bag',position(),'.txt')"/>
<xsl:result-document href="{$file}">
<xsl:value-of select="position()"/>
<xsl:for-each select="current-group()">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:result-document>
</xsl:for-each-group>
</xsl:template>
</xsl:transform>
Resulting bag1.txt
1applebananagrapecarrotpotatosquashplatesnapkinscupsshirtsshortssocks
Resulting bag2.txt
file does not exist!
Expected bag1.txt
1applebananagrapecarrot
Expected bag2.txt
2potatosquashplatesnapkins
My debugging conclusions:
It seems like position() is never 4 (each department only has 3 items)
If I change mod 4
to mod 2
I get multiple bags, and bag 1 contains 2 items. but all others but the last one contain 3 items.
each bag ends at the 2nd item of a department, all but the first bag include the last item of the previous department.
Resulting bag1.txt
1applebanana
Resulting bag1.txt
2grapecarrotpotato
Expected bag1.txt
1applebanana
Expected bag2.txt
2grapecarrot
This suggests to me that position() is related the the parent item, not to the selection. I would like position() to be related to the selection. From what I have researched, position() should be related to the selection. Like is is described in the answer here:
Final hint: position() does not tell you the position of the node within its parent. It tells you the position of the current node relative to the list of nodes you are processing right now.
Find the position of an element within its parent with XSLT / XPath
There is mention here that pattern expressions differ in their interpretation of scope compared to select expressions. After reading it, I don't know how to change my use of the pattern expression to achieve the behavior I'm expecting.
Using for-each-group for high performance XSLT
based on the behavior I currently observe:
If I had 9 fruit, 4 vegetables and 20 paper products, and used mod 5
bag1 would contain the first 5 fruit products,
bag2 would contain the last 4 fruit + 4 vegetables + the first 5 paper products.
The current behavior is not the behavior I am looking for.