Reputation: 1531
I want to calcutae the number of occurrences of a string in a particular node in XML document using XSLT. Consider this example
<mainNode>
<book>
<price> 100 </price>
<city> chennai </city>
<list>
<language> c java ruby </language>
</list>
</book>
<book>
<price> 200 </price>
<city> banglore </city>
<list>
<language> c java </language>
</list>
</book>
<book>
<price> 300 </price>
<city> delhi </city>
<list>
<language> java ruby </language>
</list>
</book>
</mainNode>
Here I want to count the occurrences of "java"
I Want Output like this:: java -- 3
How to do this??? any idea???
Upvotes: 7
Views: 21862
Reputation: 3022
maybe you can try this XSL Template to count substrings:
<xsl:template name="substring-count">
<xsl:param name="string"/>
<xsl:param name="substr"/>
<xsl:choose>
<xsl:when test="contains($string, $substr) and $string and $substr">
<xsl:variable name="rest">
<xsl:call-template name="substring-count">
<xsl:with-param name="string" select="substring-after($string, $substr)"/>
<xsl:with-param name="substr" select="$substr"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$rest + 1"/>
</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:template>
Usage:
<xsl:call-template name="substring-count">
<xsl:with-param name="string" select="'mary had a little lamb'" />
<xsl:with-param name="substr" select="'lamb'" />
</xsl:call-template>
Upvotes: 2
Reputation: 516
http://www.xsltfunctions.com/xsl/functx_number-of-matches.html
count(tokenize($arg,$pattern)) - 1
I have answered likewise here: Find the number of occurences of a substring in a string in xslt
Upvotes: 0
Reputation: 243579
Use:
count(/*/*/list/language[contains(., 'java')])
The complete XSLT transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
java -- <xsl:value-of select=
"count(/*/*/list/language[contains(., 'java')]) "/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<mainNode>
<book>
<price> 100 </price>
<city> chennai </city>
<list>
<language> c java ruby </language>
</list>
</book>
<book>
<price> 200 </price>
<city> banglore </city>
<list>
<language> c java </language>
</list>
</book>
<book>
<price> 300 </price>
<city> delhi </city>
<list>
<language> java ruby </language>
</list>
</book>
</mainNode>
the wanted, correct result is produced:
java -- 3
Update:
If we are to count all occurences of the string -- not just all nodes that contain the string -- here's how to do it:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:param name="pWord" select="' java '"/>
<xsl:template match="/">
<xsl:variable name="vResult">
<xsl:apply-templates/>
</xsl:variable>
<xsl:value-of select="concat($pWord, '--- ')"/>
<xsl:value-of select="string-length($vResult)"/>
</xsl:template>
<xsl:template match="list/language" name="countWord">
<xsl:param name="pText" select="."/>
<xsl:if test="contains($pText, $pWord)">
<xsl:text>X</xsl:text>
<xsl:call-template name="countWord">
<xsl:with-param name="pText"
select="concat(' ', substring-after($pText, $pWord))"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when this transformation is applied on this XML document:
<mainNode>
<book>
<price> 100 </price>
<city> chennai </city>
<list>
<language> c java ruby </language>
</list>
</book>
<book>
<price> 200 </price>
<city> banglore </city>
<list>
<language> c java </language>
</list>
</book>
<book>
<price> 300 </price>
<city> delhi </city>
<list>
<language> java java ruby </language>
</list>
</book>
</mainNode>
the wanted, correct result is produced:
java --- 4
Upvotes: 9
Reputation: 12075
Try this in a <xsl:value-of>
statement:
count(//language[contains(concat(' ',.,' '), ' java ')])
If your document structure is relatively static, or you have nodes called language
elsewhere that serve another purpose, you can substitute //language
for /mainNode/book/list/language
.
The concat
bit might seem a bit convoluted, but by making sure there's a space at the beginning and end of the text you're looking in, and searching for ' java '
with a space either side, you won't incorrectly include other terms that happen to include java
, such as javascript
.
If it's possible for 'java' to exist more than once in a node, then you'll need to use a recursive template. Here's one way:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:variable name="list">
<xsl:for-each select="//language">
<xsl:call-template name="count">
<xsl:with-param name="lang">java</xsl:with-param>
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="concat('java -- ',string-length($list))" />
</xsl:template>
<xsl:template name="count">
<xsl:param name="lang" />
<xsl:param name="text" select="text()" />
<xsl:if test="contains(concat(' ',$text,' '),concat(' ',$lang,' '))">
<xsl:text>0</xsl:text>
<xsl:call-template name="count">
<xsl:with-param name="lang" select="$lang" />
<xsl:with-param name="text" select="substring-after($text,$lang)" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
This essentially creates a string of 0
's, one for each occurrence of java
, and then simply uses the length of that string.
If you have the option of using XSLT 2.0, you can create a function that counts the number of occurrences in a string, and use <xsl:value-of select="sum(mycountfunction(//language))" />
or something similar.
As I pointed out in a comment on your question, a better design of the source XML would have helped significantly; none of this is necessary if each occurrence of a language had it's own element. Of course that may be outside your control, but if you have the option of changing that (or persuading the provider to change it) I'd strongly recommend it.
Upvotes: 1