Reputation: 61
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
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
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
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