Reputation: 409
I have an XML document, and I need to nest/group adjacent elements based on a specified attribute value. I know this can be done based on an element type or name, but I'm not sure how to do this with given attribute values. Sample Input:
<root>
<p id="111">5tw5t5et</p>
<p id="111">4qvtq3</p>
<p id="222">qv34tqv3</p>
<j>qv43tvq</j>
<p id="333">qv43tvq</p>
<p id="333">q34tvq43tvq</p>
<p id="333">q3434t3tvq43tvq</p>
</root>
Desired output:
<root>
<group>
<p id="111">5tw5t5et</p>
<p id="111">4qvtq3</p>
</group>
<p id="222">qv34tqv3</p>
<j>qv43tvq</j>
<group>
<p id="333">qv43tvq</p>
<p id="333">q34tvq43tvq</p>
<p id="333">q3434t3tvq43tvq</p>
</group>
</root>
I know that I can group by element name using this
<xsl:for-each-group select="*" group-adjacent="name()">
<xsl:choose>
<xsl:when test="name()='111'">
<group>
<xsl:for-each select="current-group()">
<xsl:apply-templates/>
</xsl:for-each>
</group>
</xsl:when>
<xsl:when test="name()='333'">
<group>
<xsl:for-each select="current-group()">
<xsl:apply-templates/>
</xsl:for-each>
</group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
But I'm not sure what syntax to use to group by the attribute
I've tried this:
<xsl:for-each-group select="*" group-adjacent="@id">
Which throws empty sequence errors, and this:
<xsl:for-each-group select="p" group-adjacent="@id">
Which ignores all of the non-p elements.
What is the proper way to group these elements using attribute value?
Upvotes: 2
Views: 175
Reputation: 29052
xsl:for-each-group
's group-adjacent
does not accept an empty current-grouping-key()
, so you have to handle the elements without an @id
attribute separately. This can be achieved with an if-then-else expression like if (@id) then @id else ''
or even easier (thanks to the comments) with string(@id)
.
So you can change your xsl:for-each-group
like this:
<xsl:for-each-group select="*" group-adjacent="string(@id)">
<xsl:choose>
<xsl:when test="@id='111' or @id='333'">
<group>
<xsl:apply-templates select="current-group()"/>
</group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
This assumes that an identity template is set up to copy the elements and attributes.
EDIT: Incorporated improvements suggested in the comments.
Its output is:
<group>
<p id="111">5tw5t5et</p>
<p id="111">4qvtq3</p>
</group>
<p id="222">qv34tqv3</p>
<j>qv43tvq</j>
<group>
<p id="333">qv43tvq</p>
<p id="333">q34tvq43tvq</p>
<p id="333">q3434t3tvq43tvq</p>
</group>
Upvotes: 1