John Urge
John Urge

Reputation: 11

counting sum with XSLT from XML row that have same items

I'm sorry for my english, it's not native language so some word and term may be wrong.

I have XML where is many items. and some items might be many times. I want it to be hatml table, but so that same items are listet only once and the total has been summed up.

Normal for-each is easy, but it just lists everything. How can i add some condition etc for my XSLT?

Here is my example XML:

<Amounts>
    <item>
        <id>02</id>
        <name>Item2</name>
        <amount>20</amount>
    </item>
    <item>
        <id>01</id>
        <name>Item1</name>
        <amount>80</amount>
    </item>
    <item>
        <id>06</id>
        <name>Item6</name>
        <amount>50</amount>
    </item>
    <item>
        <id>02</id>
        <name>Item2</name>
        <amount>150</amount>
    </item>
</Amounts>

And here is my XSLT now:

<table>
<tr>
    <td>id</td>
    <td>name</td>
    <td>total</td>
</tr>
<xsl:for-each select="/Amounts/item">
<tr>
    <td><xsl:value-of select="id"/></td>
    <td><xsl:value-of select="name"/></td>
    <td><xsl:value-of select="amount"/></td>
</tr>
</table>

But here is how i wan't it to be:

<table>
<tr>
    <td>id</td>
    <td>name</td>
    <td>total</td>
</tr>
<tr>
    <td>01</td>
    <td>Item1</td>
    <td>80</td>
</tr>
<tr>
    <td>02</td>
    <td>Item2</td>
    <td>170</td>
</tr>
<tr>
    <td>06</td>
    <td>Item6</td>
    <td>50</td>
</tr>   
</table>

Upvotes: 0

Views: 799

Answers (2)

SomeStupid
SomeStupid

Reputation: 101

XSLT 1.0: The principle idea of the solution is to use a key on the id attribute values, as described in https://stackoverflow.com/a/2293626/2156349 .

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="1.0"
    >
    <xsl:output method="html" indent="yes" />       
    <xsl:strip-space elements="*"/>

    <xsl:key name="itemsKey" match="item/id/text()" use="." />

    <xsl:template match="/">
        <html>
        <head>  <title>Solution</title> </head>
        <body>  <xsl:apply-templates /> </body>
        </html>
    </xsl:template>

    <xsl:template match="Amounts">
        <table>
            <tr>
                <td>id</td>
                <td>name</td>
                <td>total</td>
            </tr>
            <xsl:for-each select="item">
                <xsl:variable name="idText" select="id/text()" />
                <xsl:variable name="myId"   select="generate-id($idText)" />
                <xsl:variable name="firstId" select="generate-id(key('itemsKey',$idText)[1])" />

                <xsl:if test="$myId = $firstId">
                    <xsl:apply-templates select="." />
                </xsl:if>
            </xsl:for-each>
        </table>
    </xsl:template>

    <xsl:template match="item">
        <xsl:variable name="idValue" select="id" />
        <tr>
            <td><xsl:value-of select="id"/></td>
            <td><xsl:value-of select="name"/></td>
            <td><xsl:value-of select="sum(//item[id=$idValue]/amount)"/></td>
        </tr>
    </xsl:template>

</xsl:stylesheet>

XSLT 2.0: Using the hint of Martin Honnen from above, the whole Transformation can be simplified considerably with XSLT 2.0:

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="2.0"
    >
    <xsl:output method="html" indent="yes" />
    <xsl:strip-space elements="*"/>

    <xsl:template match="/">
        <html>
        <head> <title>Solution</title> </head>
        <body> <xsl:apply-templates /> </body>
        </html>
    </xsl:template>

    <xsl:template match="Amounts">
        <table>
            <tr>
                <td>id</td>
                <td>name</td>
                <td>total</td>
            </tr>
            <xsl:for-each-group select="item" group-by="id">
                <tr>
                    <td><xsl:value-of select="id"/></td>
                    <td><xsl:value-of select="name"/></td>
                    <td><xsl:value-of select="sum(current-group()/amount)"/></td>
                </tr>
            </xsl:for-each-group>
        </table>
    </xsl:template>

</xsl:stylesheet>

Upvotes: 0

Martin Honnen
Martin Honnen

Reputation: 167491

Using Muenchian grouping you can solve that in XSLT 1.0 with

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">

    <xsl:output method="html"/>

    <xsl:key name="group" match="item" use="id"/>

    <xsl:template match="Amounts">
        <table>
            <thead>
                <tr>
                    <th>Id</th>
                    <th>Name</th>
                    <th>Amount</th>
                </tr>
            </thead>
            <tbody>
                <xsl:apply-templates select="item[generate-id() = generate-id(key('group', id)[1])]">
                    <xsl:sort select="id" data-type="number"/>
                </xsl:apply-templates>
            </tbody>
        </table>
    </xsl:template>

    <xsl:template match="item">
        <tr>
            <td>
                <xsl:value-of select="id"/>
            </td>
            <td>
                <xsl:value-of select="name"/>
            </td>
            <td>
                <xsl:value-of select="sum(key('group', id)/amount)"/>
            </td>
        </tr>
    </xsl:template>

</xsl:stylesheet>

Upvotes: 1

Related Questions