Reputation: 27394
I have an XML with records, some records are associated with each other and, as such, I want to group them together in the output.
XML:
<Records>
<Record id="1" group="10" />
<Record id="2" group="20" />
<Record id="3" group="20" />
<Record id="4" group="20" />
</Records>
Currently, I display
<span>1</span><span>2</span><span>3</span><span>4</span>
What I would like to display is (based on the records having the same group)
<span>1</span><span>2-4</span>
I have looked into using preceding-sibling::Record/@group
to see if the grouping has changed between iterations of Record
s but am struggling to figure out how to achieve the 2-4
grouping I require.
Here is what I have so far, interspersed with some comments to illustrate what I am trying to do:
<xsl:for-each select="Records/Record">
<xsl:if test="@group != preceding-sibling::Record/@group">
<!-- obviously here we need 2-4...somehow? -->
<span><xsl:value-of="@id" /></span>
</xsl:if>
</xsl:for-each>
Upvotes: 2
Views: 197
Reputation: 243579
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kFollowing" match="Record"
use="generate-id(preceding-sibling::*
[not(@group = current()/@group)
][1])"/>
<xsl:template match="/*">
<xsl:apply-templates mode="makeGroup" select=
"Record[not(@group = preceding-sibling::*[1]/@group)]"/>
</xsl:template>
<xsl:template match="Record" mode="makeGroup">
<xsl:variable name="vGroup"
select="key('kFollowing', generate-id(preceding-sibling::*[1]))"/>
<span>
<xsl:value-of select="$vGroup[1]/@id"/>
<xsl:if test="$vGroup[2]">
<xsl:value-of select="concat('-', $vGroup[last()]/@id)"/>
</xsl:if>
</span>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<Records>
<Record id="1" group="10" />
<Record id="2" group="20" />
<Record id="3" group="20" />
<Record id="4" group="20" />
</Records>
produces the wanted, correct result:
<span>1</span><span>2-4</span>
Explanation:
This is positional grouping using a key to define all adjacent Record elements that comprise a group.
This is an efficient (sublinear) algorithm because keys are used. Algorithms using a siblings axis are typically O(N^2)
-- quadratical in time complexity and can be too slow if the total number of siblings N
is big.
Upvotes: 4
Reputation: 5130
if your nodes are allways going to be contiguous, you could use something simple like
<xsl:template match="/">
<xsl:for-each select="Records/Record">
<xsl:if test="position() = 1 or @group != preceding-sibling::Record[1]/@group">
<span><xsl:value-of select="@id" />
<xsl:if test="following-sibling::Record/@group = @group">
<xsl:variable name="following" select="following-sibling::Record[@group = ./@group]"/>
- <xsl:value-of select="$following[count($following)]/@id"/>
</xsl:if>
</span>
</xsl:if>
</xsl:for-each>
</xsl:template>
but if not you would probably need a recursive function to come up with something more robust and count the nodes manually
Upvotes: 1