Rems
Rems

Reputation: 55

Group by on multiple elements with XSL

I have an XML like this :

<state>
    <current>block</current>
    <next>air</next>
</state>
<state>
    <current>air</current>
    <next>block</next>
</state>
<state>
    <current>air</current>
    <next>swim</next>
</state>
<state>
    <current>block</current>
    <next>air</next>
</state>

So I would like to group all "current" + "next" element to distinct every current and next I can encounter (using XSLT 1.0)

air
block
swim

I already used muenchina method for grouping either "current" element or "next" element but not both at the same time.

How can I proceed ?

Upvotes: 1

Views: 2088

Answers (2)

Rems
Rems

Reputation: 55

Ok in fact it was simplier than what I expected.

I simply put an :

<xsl:key name="groups" match="//current|//next" use="." />

and then use it with :

<xsl:template match="current|next" mode="node" >
    <xsl:if test="generate-id() = generate-id(key('groups', normalize-space(.)))">
        <xsl:value-of select="."/>
    </xsl:if>
</xsl:template>

Upvotes: 1

Craig
Craig

Reputation: 4409

Just apply a sorted query and output those that are unique:

<xsl:variable name="states">
    <xsl:value-of select="//state/*[contains(name(), 'current next')]/text()"/>
</xsl:variable>

<xsl:for-each select="str:tokenize($states)">
    <xsl:sort select="text()"/>
    <xsl:if test="preceding-sibling::*/text() != text()">
        <xsl:element name="state"><xsl:value-of select="text()"/></xsl:element>
    </xsl:if>
</xsl:for-each>

Here is an update that includes an example using exsl node-set, for xslt implementations that do not support tokenize:

<xsl:variable name="states">
    <xsl:for-each select="//state/*[contains(name(), 'current next')]">
    <xsl:element name="state">
        <xsl:value-of select="text()"/>
    </xsl:element>
</xsl:variable>

<xsl:for-each select="exsl:node-set($states)/*">
    <xsl:sort select="text()"/>
    <xsl:if test="preceding-sibling::*/text() != text()">
        <xsl:element name="state"><xsl:value-of select="text()"/></xsl:element>
    </xsl:if>
</xsl:for-each>

It's also worth seeing if XSLT can sort the nodeset across multiple levels. I had originally anticipated this wouldn't work, but now not so sure, since I wonder if libxsl will flatten the nodeset first. If it doesn't, the preceding-sibling:: reference will fail, because it will only reference the immediate node, not one from the selection:

<xsl:for-each select="//state/*[contains(name(), 'current next')]">
    <xsl:sort select="text()"/>
    <xsl:if test="preceding-sibling::*/text() != text()">
        <xsl:element name="state"><xsl:value-of select="text()"/></xsl:element>
    </xsl:if>
</xsl:for-each>

Upvotes: 0

Related Questions