htmlxmlxsltxhtml

How to sum up values in XML


I have an XML file with tournament data and want to calculate the total points for each team. Here’s the XML:

<tournoi date="2012-12-12">
       <match date="2012-12-20" heure="18:00:00">
           <equipe nom="AAAA" score="3" />
           <equipe nom="BBBB" score="0" />
       </match>
       <match date="2012-12-20" heure="20:00:00">
           <equipe nom="CCCC" score="1" />
           <equipe nom="DDDD" score="1" />
       </match>
       <match date="2012-12-21" heure="18:00:00">
           <equipe nom="AAAA" score="2" />
           <equipe nom="CCCC" score="4" />
       </match>
       <match date="2012-12-21" heure="20:00:00">
           <equipe nom="BBBB" score="7" />
           <equipe nom="DDDD" score="0" />
       </match>
       <match date="2012-12-22" heure="18:00:00">
           <equipe nom="AAAA" score="3" />
           <equipe nom="DDDD" score="2" />
       </match>
       <match date="2012-12-22" heure="20:00:00">
           <equipe nom="CCCC" score="5" />
           <equipe nom="BBBB" score="1" />
       </match>
</tournoi>

Here is the XSLT code I’m using to calculate points:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="teams" match="equipe" use="@nom"/>

    <xsl:template match="/">
        <html>
            <body>
                <h2>Total points of each team</h2>
                <table border="1" width="100%">
                    <tr bgcolor="green">
                        <th>Team</th>
                        <th>Total Points</th>
                    </tr>
                    
                    <!-- Group by unique team names -->
                    <xsl:for-each select="tournoi/match/equipe[not(@nom = preceding::equipe/@nom)]">
                        <tr>
                            <td><xsl:value-of select="@nom"/></td>
                            <td>
                                <xsl:variable name="teamName" select="@nom"/>
                                <xsl:variable name="totalPoints" as="number">
                                    <xsl:for-each select="key('teams', $teamName)">
                                        <xsl:variable name="opponentScore" select="../equipe[@nom != $teamName]/@score"/>
                                        <xsl:choose>
                                            <xsl:when test="@score &gt; $opponentScore"><xsl:value-of select="2" as="number"/></xsl:when>
                                            <xsl:when test="@score = $opponentScore"><xsl:value-of select="1" as="number"/></xsl:when>
                                            <xsl:otherwise><xsl:value-of select="0" as="number"/></xsl:otherwise>
                                        </xsl:choose>
                                    </xsl:for-each>
                                </xsl:variable>
                                <xsl:value-of select="$totalPoints"/>
                            </td>
                        </tr>
                    </xsl:for-each>
                </table>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

Expected Output Example: Equipe Points AAAA 4

But currently, I’m getting 202 for AAAA instead of 4.


Solution

  • You have two separate problems here:

    1. Group the results by team;
    2. Calculate the total points for each team.

    For the first problem, you should use the Muenchian grouping method (assuming you are using an XSLT 1.0 processor). I see you did define a suitable key for this, but you are not using it.

    For the second problem, you can use the same method as here.

    Combining the two methods, the resulting stylesheet could look like this:

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
    <xsl:key name="team" match="equipe" use="@nom"/>
    <xsl:key name="wins" match="equipe[@score > ancestor::match/equipe/@score]" use="@nom"/>
    <xsl:key name="draws" match="equipe[not(@score != ancestor::match/equipe/@score)]" use="@nom"/>
    
    <xsl:template match="/tournoi">
        <html>
            <body>
                <table border="1">
                    <tr>
                        <th>Team</th>
                        <th>Total Points</th>
                    </tr>
                    <xsl:for-each select="match/equipe[count(. | key('team', @nom)[1]) = 1]">
                        <tr>
                            <td>
                                <xsl:value-of select="@nom"/>
                            </td>
                            <td>
                                <xsl:value-of select="2*count(key('wins', @nom)) + count(key('draws', @nom))"/>
                            </td>
                        </tr> 
                    </xsl:for-each> 
                </table>
            </body>
        </html>
    </xsl:template>
    
    </xsl:stylesheet>
    

    When applied to your (updated) XML input, the result will be rendered as:
    enter image description here