XSL Multiple search and replace function

10,847

Solution 1

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.

Solution 2

The definition of the translate($arg, $mapString, $transString) function is:

Returns the value of $arg modified so that every character in the value of $arg that occurs at some position N in the value of $mapString has been replaced by the character that occurs at position N in the value of $transString.

That is, it does not replace a substring with another string, but rather maps characters to other characters. For substring replacement, use something like

<xsl:template name="search-and-replace">
  <xsl:param name="str"/>
  <xsl:param name="search"/>
  <xsl:param name="replace"/>
  <xsl:choose>
    <xsl:when test="contains($str, $search)">
      <xsl:value-of select="substring-before($str, $search)"/>
      <xsl:value-of select="$replace"/>
      <xsl:call-template name="search-and-replace">
        <xsl:with-param name="str" select="substring-after($str, $search)"/>
        <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="$str"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
Share:
10,847
Admin
Author by

Admin

Updated on June 21, 2022

Comments

  • Admin
    Admin almost 2 years

    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.