Reputation: 33
I need to group data until a new data element comes and then logic repeats.
My Input XML:
<File>
<Detail>
<Data>1</Data>
<Value>4</Value>
</Detail>
<Detail>
<Data>1</Data>
<Value>7</Value>
</Detail>
<Detail>
<Data>2</Data>
<Value>4</Value>
</Detail>
<Detail>
<Data>1</Data>
<Value>5</Value>
</Detail>
<Detail>
<Data>1</Data>
<Value>1</Value>
</Detail>
<File>
The resultant ouput XML should look like:
<File>
<Detail>
<Data>1</Data>
<Value>11</Value>
</Detail>
<Detail>
<Data>2</Data>
<Value>4</Value>
</Detail>
<Detail>
<Data>1</Data>
<Value>6</Value>
</Detail>
<File>
The Values are added accordingly. As you can see the the data tags with value 1 is grouped separately as they are separated by Data tag 2.
Please help.
Upvotes: 1
Views: 302
Reputation: 122364
A common XSLT 1.0 approach to what is essentially a while loop is to use tail recursion. You start by applying a template to the first Detail
element in each group, and each element then applies templates to the next one, etc.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*" />
<xsl:output indent="yes" />
<xsl:template match="/*">
<!-- apply templates to the _first_ Detail element in each group -->
<xsl:copy>
<xsl:apply-templates select="Detail[
not(Data = preceding-sibling::Detail[1]/Data)]" />
</xsl:copy>
</xsl:template>
<!-- Detail element that is not the _last_ one in a contiguous group -->
<xsl:template match="Detail[Data = following-sibling::Detail[1]/Data]">
<xsl:param name="runningTotal" select="0" />
<xsl:apply-templates select="following-sibling::Detail[1]">
<xsl:with-param name="runningTotal" select="$runningTotal + Value" />
</xsl:apply-templates>
</xsl:template>
<!-- Detail element that _is_ the last one in a contiguous group -->
<xsl:template match="Detail">
<xsl:param name="runningTotal" select="0" />
<Detail>
<xsl:copy-of select="Data" />
<Value><xsl:value-of select="$runningTotal + Value" /></Value>
</Detail>
</xsl:template>
</xsl:stylesheet>
The idea here is that the first of the two Detail
templates will fire for elements that have the same Data
value as their immediately following sibling, and will accumulate a running total Value
passed down the chain. When we reach the last element in a group the second template will output the grand total Value
. (In the case of singleton groups like the second one in your example, the first element in the group is also the last one so we go directly to the second template with its default runningTotal
value of zero).
The XSLT 2.0 version is far simpler, as for-each-group
has native support for grouping contiguous runs:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:strip-space elements="*" />
<xsl:output indent="yes" />
<xsl:template match="/*">
<xsl:copy>
<xsl:for-each-group select="Detail" group-adjacent="Data">
<Detail>
<xsl:copy-of select="Data" />
<Value><xsl:value-of select="sum(current-group()/Value)" /></Value>
</Detail>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1
Reputation: 116992
Here's one way you could look at it:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<File>
<xsl:for-each select="File/Detail[not(Data=preceding-sibling::Detail[1]/Data)]">
<Detail>
<xsl:copy-of select="Data"/>
<xsl:variable name="start" select="count(preceding-sibling::Detail)" />
<xsl:variable name="end" select="count(following-sibling::Detail[not(Data=current()/Data)][1]/preceding-sibling::Detail)" />
<xsl:variable name="n">
<xsl:choose>
<xsl:when test="$end">
<xsl:value-of select="$end - $start - 1"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="count(following-sibling::Detail)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<Value>
<xsl:value-of select="sum(Value | following-sibling::Detail[position() <= $n]/Value)"/>
</Value>
</Detail>
</xsl:for-each>
</File>
</xsl:template>
</xsl:stylesheet>
Upvotes: 0