Manoj Pandey
Manoj Pandey

Reputation: 257

find number in a string xslt

I need to be able to find the last occurrance of a number within an element. For example,

<DATA1>States45bx3.33</DATA1>
<DATA2>States45bx33</DATA2>

From this string, I want to get the last number value in XSLT i.e 3.33 in 1st case and 33 in 2nd case. Any help will be appreciated.

Upvotes: 2

Views: 4105

Answers (2)

Maestro13
Maestro13

Reputation: 3696

OK here's a solution in XSLT 1.0

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

    <xsl:template match="/">
        <output>
            <xsl:for-each select="root/*">
                <xsl:element name="{name()}">
                    <xsl:call-template name="getEndNumber">
                        <xsl:with-param name="input" select="normalize-space(.)"/>
                        <xsl:with-param name="dotIncluded" select="true()"/>
                    </xsl:call-template>
                </xsl:element>
            </xsl:for-each>
        </output>
    </xsl:template>

    <xsl:template name="getEndNumber">
        <xsl:param name="input"/>
        <xsl:param name="dotIncluded"/>
        <xsl:variable name="len" select="string-length($input)"/>
        <xsl:choose>
            <xsl:when test="substring($input, $len, 1) &gt;= '0' and substring($input, $len, 1) &lt;= '9'">
                <xsl:call-template name="getEndNumber">
                    <xsl:with-param name="input" select="substring($input, 1, $len - 1)"/>
                    <xsl:with-param name="dotIncluded" select="$dotIncluded"/>
                </xsl:call-template>
                <xsl:value-of select="substring($input, $len, 1)"/>
            </xsl:when>
            <xsl:when test="substring($input, $len, 1) = '.' and $dotIncluded">
                <xsl:call-template name="getEndNumber">
                    <xsl:with-param name="input" select="substring($input, 1, $len - 1)"/>
                    <xsl:with-param name="dotIncluded" select="false()"/>
                </xsl:call-template>
                <xsl:value-of select="substring($input, $len, 1)"/>
            </xsl:when>
            <xsl:otherwise/>
        </xsl:choose>
    </xsl:template>

</xsl:stylesheet>

Applied to

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <a>States45bx3.33</a>
    <b>States45bx33</b>
    <c>asdfs12310.13.0sads23.23</c>
    <d>asdfs12310.13.0sads23.23z</d>
    <e>asdfs12310.13.0sads23.23.34</e>
</root>

the result is

<?xml version="1.0" encoding="UTF-8"?>
<output>
    <a>3.33</a>
    <b>33</b>
    <c>23.23</c>
    <d/>
    <e>23.34</e>
</output>

Upvotes: 1

Maestro13
Maestro13

Reputation: 3696

If you are able to apply XSLT 2.0, then make use the following:

<xsl:value-of select="replace(., '.*[^.\d](\d*\.?\d+)$', '$1')"/>

This will return a number at the end of the current node, either an integer or a floating point one.

Explanation of the regex used:
(1) .* = arbitrary number of characters at the start of string
(2) [^.\d] = one character which is not a dot and not a digit
(3) (...) grouping, allowing refer-back by $1
(4) \d*.?\d+ = optional digits + optional dot + digits
(5) $ = end of string
(6) $1 refer-back to group 1

Put into an xslt:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

    <xsl:template match="/">
        <output>
            <xsl:for-each select="root/*">
                <num>
                    <xsl:value-of select="replace(., '.*[^.\d](\d*\.?\d+)$', '$1')"/>
                </num>
            </xsl:for-each>
        </output>
    </xsl:template>

</xsl:stylesheet>

Applied to input

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <number>States45bx3.33</number>
    <number>States45bx33</number>
    <number>asdfs12310.13.0sads23.23</number>
</root>

This leads to

<?xml version="1.0" encoding="UTF-8"?>
<output>
    <num>3.33</num>
    <num>33</num>
    <num>23.23</num>
</output>

You could put this into an xsl:function (XSLT 2.0 required for this also), so that you can simply retrieve the number at the end by calling the function, and have elements surrounding the result done outside the function:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
    version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:custom="http://customFunctions">

    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

    <xsl:template match="/">
        <output>
            <xsl:for-each select="root/*">
                <num>
                    <xsl:value-of select="custom:getNumberAtEnd(.)"/>
                </num>
            </xsl:for-each>
        </output>
    </xsl:template>

    <xsl:function name="custom:getNumberAtEnd">
        <xsl:param name="input"/>
        <xsl:value-of select="replace($input, '.*[^.\d](\d*.?\d+)$', '$1')"/>
    </xsl:function>

</xsl:stylesheet>

Remark: when there is no number at the end of the string, the result of the function call or the earlier xsl:value-of statement will be the entire input string. When you don't want this, you will have to test the string first, or when you are certain that the end of the string will always be a well-formed number, then you can replace the regex used by the one below, which simply returns anything at the end after the final non-dot, non-digit, which may be an empty string.

<xsl:value-of select="replace($input, '.*[^.\d](.*)$', '$1')"/>

Upvotes: 0

Related Questions