12

I have a string in an XML file that looks similar to this:

M:Namespace.Class.Method(Something a, Something b)

The number of period (.) characters is abritrary, meaning it can be only 2 as in this example, but can be more.

I would like to use XSLT to get a substring of this string from the last '.' character, so that i will only be left with:

Method(Something a, Something b)

I could not achieve this using the standard substring/substring-after functions.

Is there an easy way to do this?

lysergic-acid
  • 19,570
  • 21
  • 109
  • 218

3 Answers3

31

In XSLT 1.0 you will need to use a recursive template, like this:

  <xsl:template name="substring-after-last">
    <xsl:param name="string" />
    <xsl:param name="delimiter" />
    <xsl:choose>
      <xsl:when test="contains($string, $delimiter)">
        <xsl:call-template name="substring-after-last">
          <xsl:with-param name="string"
            select="substring-after($string, $delimiter)" />
          <xsl:with-param name="delimiter" select="$delimiter" />
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise><xsl:value-of 
                  select="$string" /></xsl:otherwise>
    </xsl:choose>
  </xsl:template>

and invoke it like this:

<xsl:call-template name="substring-after-last">
  <xsl:with-param name="string" select="'M:Namespace.Class.Method(Something a, Something b)'" />
  <xsl:with-param name="delimiter" select="'.'" />
</xsl:call-template>

In XSLT 2.0, you can use the tokenize() function and simply select the last item in the sequence:

tokenize('M:Namespace.Class.Method(Something a, Something b)','\.')[last()]
Mads Hansen
  • 63,927
  • 12
  • 112
  • 147
  • Thanks. How do i know which version of XSLT am i using? I am invoking all of this from C# code (using the XslCompiledTransform class). – lysergic-acid Jan 31 '12 at 11:59
  • Unfortunately .NET does not support XSLT 2.0 natively. Unless you have Saxon.net, XQSharp, or other 2.0 engines you will need to use a 1.0 solution. – Mads Hansen Jan 31 '12 at 12:04
1

Here is a more efficient solution O(N) vs. O(N^2) for the accepted answer:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:template match="text()" name="skipAfterDots">
  <xsl:param name="pTotalString" select="."/>
  <xsl:param name="pTotalLength" select="string-length(.)"/>
  <xsl:param name="pPosition" select="1"/>
  <xsl:param name="pLastFound" select="-1"/>

  <xsl:choose>
    <xsl:when test="$pPosition > $pTotalLength">
      <xsl:value-of select="substring($pTotalString, $pLastFound + 1)"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:variable name="vIsDot" select=
       "substring($pTotalString, $pPosition, 1) = '.'"/>

      <xsl:call-template name="skipAfterDots">
        <xsl:with-param name="pTotalString" select="$pTotalString"/>
        <xsl:with-param name="pTotalLength" select="$pTotalLength"/>
        <xsl:with-param name="pLastFound" select=
        "$pLastFound * not($vIsDot) + $pPosition * $vIsDot"/>
        <xsl:with-param name="pPosition" select="$pPosition+1"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the following XML document:

<t>M:Namespace.Class.Method(Something a, Something b)</t>

the wanted, correct result is produced:

Method(Something a, Something b)

Explanation:

This solution doesn't contain any call to the substring-after() function. Instead, at each step only the one character of the string is compared for equality with the dot character. Because there are at most N characters, this is O(N) -- linear complexity.

On the contrary, the accepted answer calls the substring-after() function on every step. In the worst case there could be N dots and thus this would be O(N^N) -- quadratic complexity.

Note: We make the reasonable assumption that in both solutions locating the k-th character of a string is O(1).

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • 1
    Old comment, but I feel a need to comment: the accepted solution is also O(n). Each call to (a sane implementation of) `contains()` will look through leading characters until it finds a dot. After it finds a dot, every character it has looked at so far is discarded. Therefore, it will look at each character at most once. – nemetroid Jul 22 '12 at 19:51
  • @nemetroid: I am referring to ``. If there are `m` dots in the string and the string length is `n`, then this algorithm is `O(m*n)` – Dimitre Novatchev Jul 22 '12 at 20:12
  • Right, you are correct. I did not consider that `substring-after` will have to look through the entire (remaining) string. – nemetroid Jul 23 '12 at 23:01
  • @nemetroid: My explanation was misleading (mentioned `contains()` instead of `substring-after()`). Fixed now. – Dimitre Novatchev Jul 23 '12 at 23:33
0

If you do know that you have exactly two dots in your strings then you can try:

<xsl:value-of select="substring-after(substring-after($str, '.'), '.')" /> 
Igor Korkhov
  • 8,283
  • 1
  • 26
  • 31
  • This will not work if i have an arbitrary number of . in my string.. For example: Component.Something.Else.Class.Method( ... ) – lysergic-acid Jan 31 '12 at 11:46