XSLT: Finding last occurence in a string
Asked Answered
O

4

9

Given a form number like:

ABC_12345_Q-10

I want to end up with:

ABC12345

So I need to find the position of the second underscore

Note that there is no standard pattern or length to any of the "sections" between the underscores (so I cannot use substring to simply eliminate the last section).

xPath 2.0 solutions are okay.

Outmarch answered 29/6, 2010 at 14:48 Comment(2)
What happened with the first underscore in your sample?Bite
Good question (+1). See my answer for XPath 2.0 and XSLT 1.0 solutions to the more general problem when the number of underscores isn't known in advance. :)Hectograph
C
4
concat(
    substring-before($s, '_'),
    substring-before(substring-after($s, '_'), '_')
)

Alternatively:

string-join(tokenize($s, '_')[position() <= 2], '')
Chasechaser answered 29/6, 2010 at 14:51 Comment(0)
H
15

@Pavel_Minaev has provided XPath 1.0 amd XPath 2.0 solutions that work if it is known in advance that the number of underscores is 2.

Here are solutions for the more difficult problem, where the number of undrscores isn't statically known (may be any number):

XPath 2.0:

translate(substring($s,
                    1, 
                    index-of(string-to-codepoints($s), 
                             string-to-codepoints('_')
                             )[last()] -1
                   ),
          '_',
          ''
         )

XSLT 1.0:

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

  <xsl:variable name="s" select="'ABC_12345_Q-10'"/>
  <xsl:template match="/">
    <xsl:call-template name="stripLast">
     <xsl:with-param name="pText" select="$s"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template name="stripLast">
    <xsl:param name="pText"/>
    <xsl:param name="pDelim" select="'_'"/>

     <xsl:if test="contains($pText, $pDelim)">
       <xsl:value-of select="substring-before($pText, $pDelim)"/>
       <xsl:call-template name="stripLast">
         <xsl:with-param name="pText" select=
          "substring-after($pText, $pDelim)"/>
         <xsl:with-param name="pDelim" select="$pDelim"/>
       </xsl:call-template>
     </xsl:if>
   </xsl:template>
</xsl:stylesheet>

when this transformation is applied to any XML document (not used), the desired, correct result is produced:

ABC12345
Hectograph answered 29/6, 2010 at 16:35 Comment(0)
U
7

Easier solution in XSLT 2.0:

codepoints-to-string(reverse(string-to-codepoints(
    substring-before(
        codepoints-to-string(reverse(string-to-codepoints($s))), '_'))))

With 'substring-before' you will get everything after the last occurrence of your delimiter (the underscore). If you use 'substring-after' instead, you will get everything before the last occurrence of your deliminator.

Undersized answered 12/12, 2012 at 13:5 Comment(0)
C
4
concat(
    substring-before($s, '_'),
    substring-before(substring-after($s, '_'), '_')
)

Alternatively:

string-join(tokenize($s, '_')[position() <= 2], '')
Chasechaser answered 29/6, 2010 at 14:51 Comment(0)
A
0

Generalized one -

substring($string,1, string-length($string)-string-length(str:split($string, '_')[count(str:split($string, '_'))]))

Idea is to get the index of last occurrence by splitting string.

Index of last occurrence = string-length($string) - length of last string after split

Aerostat answered 10/8, 2017 at 22:28 Comment(1)
A solution that depends on an extension function that only a few processors support is hardly a "generalized one".Horticulture

© 2022 - 2024 — McMap. All rights reserved.