In 2.0 you can use xsl:analyze-string:
<xsl:variable name="hits" as="text()*">
<xsl:analyze-string select="..the text.." regex="..the pattern..">
<xsl:matching-substring>1</xsl:matching-substring>
<xsl:non-matching-substring/>
</xsl:analyze-string>
</xsl:variable>
There were <xsl:value-of select="count($hits)"/> hits
In 1.0 your approach of recursion using substring-before/after is the only way to do it.
Michael Kay
http://www.saxonica.com/
Author, XSLT Programmer's Reference and XPath 2.0 Programmer's Reference