tags:

views:

274

answers:

3

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>
A: 

I'm not sure of way to do this kind of aggregation using only XSLT functions. It would be really easy to do in C#, though.

David
+1  A: 

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)" />
Gavin Miller
+1  A: 

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"&gt;
  <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.

Jukka Matilainen
+1, even though I like `generate-id()` better. The question looks a bit like homework to me.
Tomalak