Search code examples
searchxsltreplacetranslate

XSL Multiple search and replace function


I am attempting to use the XSL translate() function to create something like a search and replace function as follows:

<xsl:template name="create-id">
    <xsl:param name="id" />
    <xsl:call-template name="search-and-replace">
        <xsl:with-param name="str" select="$id" />
        <xsl:with-param name="search">0123456789</xsl:with-param>
        <xsl:with-param name="replace">abcdefghij</xsl:with-param>
    </xsl:call-template>
</xsl:template>

<xsl:template name="search-and-replace">
    <xsl:param name="str" />
    <xsl:param name="search" />
    <xsl:param name="replace" />
    <xsl:variable name="newstr" select="translate($str, $search,
    $replace)" />
    <xsl:choose>
        <xsl:when test="contains($newstr, $search)">
            <xsl:call-template name="search-and-replace">
                <xsl:with-param name="str" select="$newstr" />
                <xsl:with-param name="search" select="$search" />
                <xsl:with-param name="replace" select="$replace" />
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$newstr" />
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

However, something about my logic is wrong here as it appears to be stripping off the last character in the returned string. My guess is that translate() is only replacing the first instance of each character in the string and is not truly recursive.

Any thoughts or input would be appreciated.


Solution

  • The translate() function can only replace a single character with another single character (or with the empty character (delete)). Thus it cannot solve the problem of string replacement.

    Here is a complete XSLT 1.0 solution to the multiple-replace problem:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:my="my:my">
        <xsl:output omit-xml-declaration="yes"/>
        <xsl:strip-space elements="*"/>
    
        <my:params xml:space="preserve">
            <pattern>
                <old>&#xA;</old>
                <new><br/></new>
            </pattern>
            <pattern>
                <old>quick</old>
                <new>slow</new>
            </pattern>
            <pattern>
                <old>fox</old>
                <new>elephant</new>
            </pattern>
            <pattern>
                <old>brown</old>
                <new>white</new>
            </pattern>
        </my:params>
    
        <xsl:variable name="vPats"
             select="document('')/*/my:params/*"/>
    
        <xsl:template match="text()" name="multiReplace">
            <xsl:param name="pText" select="."/>
            <xsl:param name="pPatterns" select="$vPats"/>
    
            <xsl:if test="string-length($pText) >0">
                <xsl:variable name="vPat" select=
                "$vPats[starts-with($pText, old)][1]"/>
    
                <xsl:choose>
                    <xsl:when test="not($vPat)">
                        <xsl:copy-of select="substring($pText,1,1)"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:copy-of select="$vPat/new/node()"/>
                    </xsl:otherwise>
                </xsl:choose>
    
                <xsl:call-template name="multiReplace">
                    <xsl:with-param name="pText" select=
                    "substring($pText, 1 + not($vPat) + string-length($vPat/old/node()))"/>
                </xsl:call-template>
            </xsl:if>
        </xsl:template>
    </xsl:stylesheet>
    

    when this transformation is applied on the following XML document:

    <t>The quick
    brown fox</t>
    

    the wanted, correct result is produced:

    The slow<br />white elephant
    

    Explanation:

    1. A named template that calls itself recursively is used.

    2. All multiple replacement pattern --> replacement pairs are provided in a single external parameter, which for convenience here is specified inline as the global-level element <my:params> .

    3. The recursion takes every single character in the source string (from left to right) and finds the first pattern that starts with this character at this position in the string.

    4. The replacement can be not only a string but also any node. In this specific case we are replacing every NL character with a <br/> element.