How to Group in XSLT
If you search on the Internet in general, you will see that there is an XSLT 2.0 function called <xsl:for-each-group>, unfortunately the XSLT templating engine used by Spira is based on Microsoft .NET which still only supports XSLT 1.0.
Luckily there is a solution for grouping data that only relies on XSLT 1.0 functions, call the Muenchian grouping approach.
This means we use the following construct:
<xsl:key name="groups" match="/RequirementData/Requirement" use="ComponentName" />
<xsl:template match="/RequirementData">
<xsl:apply-templates select="Requirement[generate-id() = generate-id(key('groups', ComponentName)[1])]"/>
</xsl:template>
<xsl:template match="Requirement">
<h1><xsl:value-of select="ComponentName"/></h1>
<table id="{ComponentName}" class="DataGrid" style="width:100%">
<tr>
<th>Req #</th>
<th>Name</th>
<th>Type</th>
<th>Priority</th>
<th>Status</th>
<th>Author</th>
<th>Owner</th>
<th>Creation Date</th>
<th>Last Modified</th>
<th>Release #</th>
</tr>
<xsl:for-each select="key('groups', ComponentName)">
... rest of report template, to display requirement fields
</xsl:for-each>
</table>
</xsl:template>
This will loop through the Component fields, find each unique value, and then display a table of matching requirements under each component.
You can also do the same thing for a custom field (e.g. "Difficulty") by replacing ComponentName with something like:
CustomProperties/CustomProperty[Alias='Difficulty']/Value
Putting this all together would give:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:key name="groups" match="/RequirementData/Requirement" use="ComponentName" />
<xsl:template match="/RequirementData">
<xsl:apply-templates select="Requirement[generate-id() = generate-id(key('groups', ComponentName)[1])]"/>
</xsl:template>
<xsl:template match="Requirement">
<h1><xsl:value-of select="ComponentName"/></h1>
<table id="{ComponentName}" class="DataGrid" style="width:100%">
<tr>
<th>Req #</th>
<th>Name</th>
<th>Type</th>
<th>Priority</th>
<th>Status</th>
<th>Author</th>
<th>Owner</th>
<th>Creation Date</th>
<th>Last Modified</th>
<th>Release #</th>
</tr>
<xsl:for-each select="key('groups', ComponentName)">
<tr>
<td>
<xsl:value-of select="RequirementId"/>
</td>
<td>
<xsl:attribute name="style">
padding-left: <xsl:value-of select="string-length(IndentLevel)*2"/>px;
</xsl:attribute>
<xsl:if test="IsSummary='True'">
<b>
<xsl:value-of select="Name"/>
</b>
</xsl:if>
<xsl:if test="IsSummary='False'">
<xsl:value-of select="Name"/>
</xsl:if>
</td>
<td>
<xsl:value-of select="RequirementTypeName"/>
</td>
<td>
<xsl:value-of select="ImportanceName"/>
</td>
<td>
<xsl:value-of select="RequirementStatusName"/>
</td>
<td>
<xsl:value-of select="AuthorName"/>
</td>
<td>
<xsl:value-of select="OwnerName"/>
</td>
<td class="Date">
<xsl:call-template name="format-date">
<xsl:with-param name="datetime" select="CreationDate" />
</xsl:call-template>
</td>
<td class="Date">
<xsl:call-template name="format-date">
<xsl:with-param name="datetime" select="LastUpdateDate" />
</xsl:call-template>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="format-date">
<xsl:param name="datetime"/>
<xsl:variable name="date" select="substring-before($datetime, 'T')" />
<xsl:variable name="year" select="substring-before($date, '-')" />
<xsl:variable name="month" select="substring-before(substring-after($date, '-'), '-')" />
<xsl:variable name="day" select="substring-after(substring-after($date, '-'), '-')" />
<xsl:variable name="time" select="substring-before(substring-after($datetime, 'T'), '.')" />
<xsl:variable name="monthname">
<xsl:choose>
<xsl:when test="$month='01'">
<xsl:value-of select="'Jan'"/>
</xsl:when>
<xsl:when test="$month='02'">
<xsl:value-of select="'Feb'"/>
</xsl:when>
<xsl:when test="$month='03'">
<xsl:value-of select="'Mar'"/>
</xsl:when>
<xsl:when test="$month='04'">
<xsl:value-of select="'Apr'"/>
</xsl:when>
<xsl:when test="$month='05'">
<xsl:value-of select="'May'"/>
</xsl:when>
<xsl:when test="$month='06'">
<xsl:value-of select="'Jun'"/>
</xsl:when>
<xsl:when test="$month='07'">
<xsl:value-of select="'Jul'"/>
</xsl:when>
<xsl:when test="$month='08'">
<xsl:value-of select="'Aug'"/>
</xsl:when>
<xsl:when test="$month='09'">
<xsl:value-of select="'Sep'"/>
</xsl:when>
<xsl:when test="$month='10'">
<xsl:value-of select="'Oct'"/>
</xsl:when>
<xsl:when test="$month='11'">
<xsl:value-of select="'Nov'"/>
</xsl:when>
<xsl:when test="$month='12'">
<xsl:value-of select="'Dec'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="''" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="concat($day, '-' ,$monthname, '-', $year , ' ', $time)" />
</xsl:template>
</xsl:stylesheet>
Sample Output
When you run this report you will get something like:
Custom Property Version
The full version using the "Difficulty" custom property would be:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:key name="groups" match="/RequirementData/Requirement" use="CustomProperties/CustomProperty[Alias='Difficulty']/Value" />
<xsl:template match="/RequirementData">
<xsl:apply-templates select="Requirement[generate-id() = generate-id(key('groups', CustomProperties/CustomProperty[Alias='Difficulty']/Value)[1])]"/>
</xsl:template>
<xsl:template match="Requirement">
<h1><xsl:value-of select="CustomProperties/CustomProperty[Alias='Difficulty']/Value"/></h1>
<table id="{CustomProperties/CustomProperty[Alias='Difficulty']/Value}" class="DataGrid" style="width:100%">
<tr>
<th>Req #</th>
<th>Name</th>
<th>Type</th>
<th>Priority</th>
<th>Status</th>
<th>Author</th>
<th>Owner</th>
<th>Creation Date</th>
<th>Last Modified</th>
<th>Release #</th>
</tr>
<xsl:for-each select="key('groups', CustomProperties/CustomProperty[Alias='Difficulty']/Value)">
<tr>
<td>
<xsl:value-of select="RequirementId"/>
</td>
<td>
<xsl:attribute name="style">
padding-left: <xsl:value-of select="string-length(IndentLevel)*2"/>px;
</xsl:attribute>
<xsl:if test="IsSummary='True'">
<b>
<xsl:value-of select="Name"/>
</b>
</xsl:if>
<xsl:if test="IsSummary='False'">
<xsl:value-of select="Name"/>
</xsl:if>
</td>
<td>
<xsl:value-of select="RequirementTypeName"/>
</td>
<td>
<xsl:value-of select="ImportanceName"/>
</td>
<td>
<xsl:value-of select="RequirementStatusName"/>
</td>
<td>
<xsl:value-of select="AuthorName"/>
</td>
<td>
<xsl:value-of select="OwnerName"/>
</td>
<td class="Date">
<xsl:call-template name="format-date">
<xsl:with-param name="datetime" select="CreationDate" />
</xsl:call-template>
</td>
<td class="Date">
<xsl:call-template name="format-date">
<xsl:with-param name="datetime" select="LastUpdateDate" />
</xsl:call-template>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="format-date">
<xsl:param name="datetime"/>
<xsl:variable name="date" select="substring-before($datetime, 'T')" />
<xsl:variable name="year" select="substring-before($date, '-')" />
<xsl:variable name="month" select="substring-before(substring-after($date, '-'), '-')" />
<xsl:variable name="day" select="substring-after(substring-after($date, '-'), '-')" />
<xsl:variable name="time" select="substring-before(substring-after($datetime, 'T'), '.')" />
<xsl:variable name="monthname">
<xsl:choose>
<xsl:when test="$month='01'">
<xsl:value-of select="'Jan'"/>
</xsl:when>
<xsl:when test="$month='02'">
<xsl:value-of select="'Feb'"/>
</xsl:when>
<xsl:when test="$month='03'">
<xsl:value-of select="'Mar'"/>
</xsl:when>
<xsl:when test="$month='04'">
<xsl:value-of select="'Apr'"/>
</xsl:when>
<xsl:when test="$month='05'">
<xsl:value-of select="'May'"/>
</xsl:when>
<xsl:when test="$month='06'">
<xsl:value-of select="'Jun'"/>
</xsl:when>
<xsl:when test="$month='07'">
<xsl:value-of select="'Jul'"/>
</xsl:when>
<xsl:when test="$month='08'">
<xsl:value-of select="'Aug'"/>
</xsl:when>
<xsl:when test="$month='09'">
<xsl:value-of select="'Sep'"/>
</xsl:when>
<xsl:when test="$month='10'">
<xsl:value-of select="'Oct'"/>
</xsl:when>
<xsl:when test="$month='11'">
<xsl:value-of select="'Nov'"/>
</xsl:when>
<xsl:when test="$month='12'">
<xsl:value-of select="'Dec'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="''" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="concat($day, '-' ,$monthname, '-', $year , ' ', $time)" />
</xsl:template>
</xsl:stylesheet>
Running this version gives the following output: