Reputation: 55
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
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
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