user182093
user182093

Reputation: 61

Node count and occurence - XSL

I need to write generic xsl that would take in an xml document and output the count of nodes and their names. So if I have a file like the following:

   <assets>
    <asset>
        <type>hardware</type>
        <item>
            <name>HP laptop</name>
            <value>799</value>
        </item>
        <item>
            <name>server</name>
            <value>1000</value>
        </item>
        <item>
            <name>ViewSonic Monitor</name>
            <value>399</value>
        </item>
    </asset>
    <asset>
        <type>software</type>
        <item>
            <name>Windows Vista</name>
            <value>399</value>
        </item>
        <item>
            <name>Office XP</name>
            <value>499</value>
        </item>
        <item>
            <name>Windows 7</name>
            <value>399</value>
        </item>
          <item>
            <name>MS Project Professional 2007</name>
            <value>299</value>
          </item>
       </asset>
    </assets>

The output would be:

   <output>
    <node name="assets" count="1"/>
    <node name="asset" count="2"/>
    <node name= "type" count="??"/>
    <node name="item" count=??/>
    <node name="name" count=??/>
    <node name="value" count=??/>
    </output>

Upvotes: 6

Views: 50099

Answers (3)

Mohamed
Mohamed

Reputation: 864

This is my solution using XSLT 2.0 :

<?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" version="2.0">

   <xsl:output method="xml" indent="yes"/>

   <xsl:template match="/">
      <xsl:element name="output">
         <xsl:for-each-group select="//*" group-by="name()">
            <xsl:element name="node">
               <xsl:attribute name="name">
                  <xsl:value-of select="current-grouping-key()"/>
               </xsl:attribute>
               <xsl:attribute name="count">
                  <xsl:value-of select="count(current-group())"/>
               </xsl:attribute>
            </xsl:element>
         </xsl:for-each-group>
      </xsl:element>
   </xsl:template>

</xsl:stylesheet>

Upvotes: 1

Jukka Matilainen
Jukka Matilainen

Reputation: 10198

The generic solution for input containing nodes with any names can be done using the Muenchian method:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:key name="nodes-by-name" match="*" use="name()"/>
  <xsl:template match="/">
    <output>
      <xsl:for-each select="//*[count(.|key('nodes-by-name', name())[1]) = 1]">
        <node name="{name()}" count="{count(key('nodes-by-name', name()))}"/>
      </xsl:for-each>
    </output>
  </xsl:template>
</xsl:stylesheet>

Explanation: Using xsl:key, create a mapping from names to the nodes having that name. Then iterate through all unique names, and output the node count for the name. The main trick here is how to iterate through unique names. See the linked page for an explanation of the count(.|foo)=1 idiom used to figure out if foo is a node set containing only the context node.

Upvotes: 7

Gavin Miller
Gavin Miller

Reputation: 43865

You'll want to use the count function:

<xsl:value-of select="count(assets/asset)" />

So your code would look like:

Assets: <xsl:value-of select="count(assets)" />
Asset:  <xsl:value-of select="count(assets/asset)" />
Item:   <xsl:value-of select="count(assets/asset/item)" />

Upvotes: 10

Related Questions