DigitalMC
DigitalMC

Reputation: 877

XSL - Adding Values of nodes based on condition

So basically I need a function that loops through XML nodes and IF a condition is true, it adds that value to a variable. I am aggregating social posts and need to count how many of each social post is in the feed. Here is my XML:

 <feed>
   <channel>
     <sources>
       <source>
           <name>Facebook</name>
           <count>3</count>
       </source>
       <source>
           <name>Twitter</name>
           <count>2</count>
       </source>
       <source>
           <name>Twitter</name>
           <count>8</count>
        </source>
     </sources>
   </channel>
  </feed>

The catch is the same source can appear multiple times, and I need to add those together. So I would need a twitter count of 10 for the above XML. Here is where I am at so far:

<xsl:variable name="num_tw">
<xsl:for-each select="feed/channel/sources/source">
  <xsl:choose>
    <xsl:when test="name, 'twitter')">
      <xsl:value-of select="count"/>
    </xsl:when>
    <xsl:otherwise></xsl:otherwise>
  </xsl:choose>
</xsl:for-each>
</xsl:variable>

<xsl:variable name="num_fb">
<xsl:for-each select="feed/channel/sources/source">
  <xsl:choose>
    <xsl:when test="name, 'facebook')">
      <xsl:value-of select="count"/>
    </xsl:when>
    <xsl:otherwise></xsl:otherwise>
  </xsl:choose>
</xsl:for-each>
</xsl:variable>

This doesn't work because if there is two twitter feeds it puts the numbers side by side and outputs "28" instead of "10". Any help is appreciated!

Upvotes: 1

Views: 5632

Answers (1)

Tim C
Tim C

Reputation: 70648

You don't need to iterate over the nodes with xsl:for-each here. Instead you can just make use of the sum operator. For example, your num_tw variable can just be re-written like so

<xsl:variable name="num_tw" select="sum(feed/channel/sources/source[name='Twitter']/count)"/>

However, do you really want to hard-code your feed names here? This is really a 'grouping' issue, and in XSLT 1.0 you use a technique called Muencian Grouping to solve it. In your case, you want to group source elements by their name element, and so you define a key like so:

<xsl:key name="source" match="source" use="name" />

Then, you look at all the source elements, and pick the one that is first in the group for their given name element:

<xsl:apply-templates 
   select="feed/channel/sources/source[generate-id() = generate-id(key('source', name)[1])]" />

Then, within the template that matches this, you can sum up the counts like so:

<xsl:value-of select="sum(key('source', name)/count)" />

Here is the full XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>
    <xsl:key name="source" match="source" use="name" />

    <xsl:template match="/">
    <xsl:apply-templates select="feed/channel/sources/source[generate-id() = generate-id(key('source', name)[1])]" />

    </xsl:template>

    <xsl:template match="source">
        <source>
            <xsl:copy-of select="name" />
            <count><xsl:value-of select="sum(key('source', name)/count)" /></count>
        </source>
    </xsl:template>
</xsl:stylesheet>

When applied to your XML, the following is output:

<source>
   <name>Facebook</name>
   <count>3</count>
</source>
<source>
   <name>Twitter</name>
   <count>10</count>
</source>

Note that if you did really want to find out the count of a specific feed, like 'Facebook' it would still be preferably to use the key here. For example:

<xsl:variable name="num_fb" select="sum(key('source', 'Facebook')/count)"/>

Upvotes: 4

Related Questions