Reputation: 11
I have the following XML structure
<containers>
<container type="1"></container>
<container type="2"></container>
<container type="2"></container>
<container type="1"></container>
<container type="2"></container>
<container type="2"></container>
<container type="2"></container>
</containers>
I want to find the count of immediate preceding-sibling of the same type. How can check in XSLT e.g.
my XSLT condition
<xsl:choose>
<xsl:when test="count(preceding-sibling::*[type = 2]) mod 2 = 0">
<xsl:attribute name="class">classX</xsl:attribute>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
Upvotes: 0
Views: 735
Reputation: 29052
In XSLT-2.0 this is easy:
<xsl:template match="containers">
<xsl:copy>
<xsl:for-each-group select="container" group-adjacent="@type">
<xsl:for-each select="current-group()">
<container>
<xsl:copy-of select="@*" />
<xsl:attribute name="class"><xsl:value-of select="position()-1" /></xsl:attribute>
</container>
</xsl:for-each>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
Its output is:
<?xml version="1.0" encoding="UTF-8"?>
<containers>
<container type="1" class="0"/>
<container type="2" class="0"/>
<container type="2" class="1"/>
<container type="1" class="0"/>
<container type="2" class="0"/>
<container type="2" class="1"/>
<container type="2" class="2"/>
</containers>
An XSLT-1.0 solution is this (Inspired by this SO answer):
<xsl:template match="containers">
<xsl:copy>
<xsl:for-each select="container">
<container>
<xsl:copy-of select="@*" />
<xsl:attribute name="class"><xsl:value-of select="count(preceding-sibling::container) - count(preceding-sibling::container[@type!=current()/@type][1]/preceding-sibling::container | preceding-sibling::container[@type!=current()/@type][1])" /></xsl:attribute>
</container>
</xsl:for-each>
</xsl:copy>
</xsl:template>
It is more complicated and slower - and far less elegant. But it does its job. The output is the same.
Upvotes: 1
Reputation: 167716
In XSLT 3, you could implement it using an accumulator:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy" streamable="yes" use-accumulators="type-count"/>
<xsl:accumulator name="type-count" as="map(*)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule match="containers" select="map {}"/>
<xsl:accumulator-rule
match="containers/container"
select="map {
'type' : string(@type),
'count' :
if (not(@type = $value?type))
then 0
else $value?count + 1
}"/>
</xsl:accumulator>
<xsl:template match="container">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:attribute name="sibling-count" select="accumulator-before('type-count')?count"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/gWEaSuW
Or, for your example, to check and create an attribute:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy" streamable="yes" use-accumulators="type-count"/>
<xsl:accumulator name="type-count" as="map(*)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule match="containers" select="map {}"/>
<xsl:accumulator-rule
match="containers/container"
select="map {
'type' : string(@type),
'count' :
if (not(@type = $value?type))
then 0
else $value?count + 1
}"/>
</xsl:accumulator>
<xsl:template match="container">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:if test="accumulator-before('type-count')?count mod 2 = 0">
<xsl:attribute name="class">classX</xsl:attribute>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/gWEaSuW/1
Upvotes: 0