用递归替换XSLT多字符串

用递归替换XSLT多字符串,xslt,recursion,substring,tail-recursion,Xslt,Recursion,Substring,Tail Recursion,我一直试图用递归执行多个不同的字符串替换,但遇到了一个障碍。我已经成功地让第一个替代品开始工作,但随后的替代品从未开火。我知道这与递归以及with param字符串如何传递回调用模板有关。我看到了我的错误以及为什么下一个xsl:when从未触发,但我似乎无法准确地理解如何将完整修改的字符串从第一个xsl:when传递到第二个xsl:when。非常感谢您的帮助 <xsl:template name="replace"> <xsl:param name="string" s

我一直试图用递归执行多个不同的字符串替换,但遇到了一个障碍。我已经成功地让第一个替代品开始工作,但随后的替代品从未开火。我知道这与递归以及with param字符串如何传递回调用模板有关。我看到了我的错误以及为什么下一个xsl:when从未触发,但我似乎无法准确地理解如何将完整修改的字符串从第一个xsl:when传递到第二个xsl:when。非常感谢您的帮助

<xsl:template name="replace">
    <xsl:param name="string" select="." />
    <xsl:choose>
        <xsl:when test="contains($string, '&#13;&#10;')">
            <xsl:value-of select="substring-before($string, '&#13;&#10;')" />
            <br/>
            <xsl:call-template name="replace">
                <xsl:with-param name="string" select="substring-after($string, '&#13;&#10;')"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:when test="contains($string, 'TXT')">
            <xsl:value-of select="substring-before($string, '&#13;TXT')" />
            <xsl:call-template name="replace">
                <xsl:with-param name="string" select="substring-after($string, '&#13;')" />
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$string"/>
        </xsl:otherwise>

    </xsl:choose>
</xsl:template>

此样式表显示了一个详细的解决方案,供您了解模式:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="text()" name="replace">
        <xsl:param name="pString" select="string()"/>
        <xsl:param name="pSearch" select="'THIS'"/>
        <xsl:param name="pReplace" select="'THAT'"/>
        <xsl:choose>
            <xsl:when test="contains($pString, '&#xA;')">
                <xsl:call-template name="replace">
                    <xsl:with-param
                         name="pString"
                         select="substring-before($pString, '&#xA;')"/>
                </xsl:call-template>
                <br/>
                <xsl:call-template name="replace">
                    <xsl:with-param
                         name="pString"
                         select="substring-after($pString, '&#xA;')"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:when test="contains($pString, $pSearch)">
                <xsl:call-template name="replace">
                    <xsl:with-param
                         name="pString"
                         select="substring-before($pString, $pSearch)"/>
                </xsl:call-template>
                <xsl:value-of select="$pReplace"/>
                <xsl:call-template name="replace">
                    <xsl:with-param
                         name="pString"
                         select="substring-after($pString, $pSearch)"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$pString"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>
通过此输入:

<t>THIS is a test.
But THAT is not.
THIS is also a test.</t>
输出:

<t>THAT is a test.<br />But THAT is not.<br />THAT is also a test.</t>
<t>THAT is a test.<br/>But THAT is not.<br/>THAT is also a test.</t>
编辑:完全参数化的解决方案

<stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform">
    <param name="pMap">
        <s t="&#xA;" xmlns=""><br/></s>
        <s t="THIS" xmlns="">THAT</s>
    </param>
    <template match="node()|@*">
        <copy>
            <apply-templates select="node()|@*"/>
        </copy>
    </template>
    <template match="text()" name="replace">
        <param name="pString" select="string()"/>
        <param name="pSearches"
                   select="document('')/*/*[@name='pMap']/s"/>
        <param name="vMatch" select="$pSearches[contains($pString,@t)][1]"/>
        <choose>
            <when test="$vMatch">
                <call-template name="replace">
                    <with-param
                         name="pString"
                         select="substring-before($pString, $vMatch/@t)"/>
                </call-template>
                <copy-of select="$vMatch/node()"/>
                <call-template name="replace">
                    <with-param
                         name="pString"
                         select="substring-after($pString, $vMatch/@t)"/>
                </call-template>
            </when>
            <otherwise>
                <value-of select="$pString"/>
            </otherwise>
        </choose>
    </template>
</stylesheet>
输出:

<t>THAT is a test.<br />But THAT is not.<br />THAT is also a test.</t>
<t>THAT is a test.<br/>But THAT is not.<br/>THAT is also a test.</t>

注意:在XML 1.0中使用内联数据时有一个问题:不能像XML 1.1中那样重置带前缀的命名空间声明。解决方案是使用一种不常见但有效的表示法:将XSLT名称空间声明为默认名称空间。

此样式表显示了一个详细的解决方案,供您了解模式:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="text()" name="replace">
        <xsl:param name="pString" select="string()"/>
        <xsl:param name="pSearch" select="'THIS'"/>
        <xsl:param name="pReplace" select="'THAT'"/>
        <xsl:choose>
            <xsl:when test="contains($pString, '&#xA;')">
                <xsl:call-template name="replace">
                    <xsl:with-param
                         name="pString"
                         select="substring-before($pString, '&#xA;')"/>
                </xsl:call-template>
                <br/>
                <xsl:call-template name="replace">
                    <xsl:with-param
                         name="pString"
                         select="substring-after($pString, '&#xA;')"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:when test="contains($pString, $pSearch)">
                <xsl:call-template name="replace">
                    <xsl:with-param
                         name="pString"
                         select="substring-before($pString, $pSearch)"/>
                </xsl:call-template>
                <xsl:value-of select="$pReplace"/>
                <xsl:call-template name="replace">
                    <xsl:with-param
                         name="pString"
                         select="substring-after($pString, $pSearch)"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$pString"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>
通过此输入:

<t>THIS is a test.
But THAT is not.
THIS is also a test.</t>
输出:

<t>THAT is a test.<br />But THAT is not.<br />THAT is also a test.</t>
<t>THAT is a test.<br/>But THAT is not.<br/>THAT is also a test.</t>
编辑:完全参数化的解决方案

<stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform">
    <param name="pMap">
        <s t="&#xA;" xmlns=""><br/></s>
        <s t="THIS" xmlns="">THAT</s>
    </param>
    <template match="node()|@*">
        <copy>
            <apply-templates select="node()|@*"/>
        </copy>
    </template>
    <template match="text()" name="replace">
        <param name="pString" select="string()"/>
        <param name="pSearches"
                   select="document('')/*/*[@name='pMap']/s"/>
        <param name="vMatch" select="$pSearches[contains($pString,@t)][1]"/>
        <choose>
            <when test="$vMatch">
                <call-template name="replace">
                    <with-param
                         name="pString"
                         select="substring-before($pString, $vMatch/@t)"/>
                </call-template>
                <copy-of select="$vMatch/node()"/>
                <call-template name="replace">
                    <with-param
                         name="pString"
                         select="substring-after($pString, $vMatch/@t)"/>
                </call-template>
            </when>
            <otherwise>
                <value-of select="$pString"/>
            </otherwise>
        </choose>
    </template>
</stylesheet>
输出:

<t>THAT is a test.<br />But THAT is not.<br />THAT is also a test.</t>
<t>THAT is a test.<br/>But THAT is not.<br/>THAT is also a test.</t>

注意:在XML 1.0中使用内联数据时有一个问题:不能像XML 1.1中那样重置带前缀的命名空间声明。解决方案是使用一种不常见但有效的表示法:将XSLT名称空间声明为默认名称空间。

该问题可能源于换行符编码的差异,导致XSLT处理器无法识别匹配字符串中的CRLF。我建议用逗号代替换行符进行测试。使用参数abc、def、ghi调用时,以下内容将为您提供预期结果:

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

问题可能源于换行符编码的差异,导致XSLT处理器无法识别匹配字符串中的CRLF。我建议用逗号代替换行符进行测试。使用参数abc、def、ghi调用时,以下内容将为您提供预期结果:

<xsl:template name="replace">
  <xsl:param name="string" select="." />
  <xsl:choose>
    <xsl:when test="contains($string, ',')">
        <xsl:value-of select="substring-before($string, ',')" />
        <br/>
        <xsl:call-template name="replace">
            <xsl:with-param name="string" select="substring-after($string, ',')"/>
        </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
        <xsl:value-of select="$string"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
此转换是完全参数化的,不需要使用默认名称空间的任何技巧:

按照上述顺序和文本:

   "corelation"
然后,此解决方案会产生更正确的结果:

"similarity"
@Alejandro目前接受的解决方案产生:

"comapping"
编辑:通过一个小的更新,我们得到了另一个改进:如果在给定的位置有多个替换是可能的,我们将执行最长的替换

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common"
 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="vrtfPats">
     <xsl:for-each select="document('')/*/my:params/*">
      <xsl:sort select="string-length(old)"
           data-type="number" order="descending"/>
       <xsl:copy-of select="."/>
     </xsl:for-each>
    </xsl:variable>

    <xsl:variable name="vPats" select=
     "ext:node-set($vrtfPats)/*"/>

    <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>
因此,如果我们有两个rep,例如core->kernel和coreration->similarity,那么第二个rep将用于包含单词coreration的文本,而不管rep是如何排序的。

此转换是完全参数化的,不需要使用默认名称空间的任何技巧:

按照上述顺序和文本:

   "corelation"
然后,此解决方案会产生更正确的结果:

"similarity"
@Alejandro目前接受的解决方案产生:

"comapping"
编辑:通过一个小的更新,我们得到了另一个改进:如果在给定的位置有多个替换是可能的,我们将执行最长的替换

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common"
 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="vrtfPats">
     <xsl:for-each select="document('')/*/my:params/*">
      <xsl:sort select="string-length(old)"
           data-type="number" order="descending"/>
       <xsl:copy-of select="."/>
     </xsl:for-each>
    </xsl:variable>

    <xsl:variable name="vPats" select=
     "ext:node-set($vrtfPats)/*"/>

    <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>

因此,如果我们有两个rep,比如core->kernel和coreration->similarity,那么第二个rep将用于包含单词coreration的文本,而不管rep的顺序如何。

我修改了Dimitrie的答案,将他的解决方案放在模板中并使用exsl扩展。请检查,可能对某人有用

<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:exsl="http://exslt.org/common"
    xmlns:a="http://www.tralix.com/cfd/2" 
    extension-element-prefixes="exsl">
    <xsl:output indent="yes"/>
    <xsl:template match="/*">
        <xsl:variable name="replacesList">
            <replaces>
                <replace><old>01</old><new>01 - Efectivo</new></replace>
                <replace><old>02</old><new>02 - Cheque nominativo</new></replace>
                <replace><old>03</old><new>03 - Transferencia electrónica de fondos</new></replace>
                <replace><old>04</old><new>04 - Tarjeta de Crédito</new></replace>
                <replace><old>05</old><new>05 - Monedero Electrónico</new></replace>
                <replace><old>06</old><new>06 - Dinero electrónico</new></replace>
                <replace><old>08</old><new>08 - Vales de despensa</new></replace>
                <replace><old>28</old><new>28 - Tarjeta de Débito</new></replace>
                <replace><old>29</old><new>29 - Tarjeta de Servicio</new></replace>
                <replace><old>99</old><new>99 - Otros</new></replace>
            </replaces>
        </xsl:variable>     
        <descripcionMetodoDePago>
            <xsl:call-template name="replaces">
                <xsl:with-param name="text" select="text"/>
                <xsl:with-param name="replaces">
                    <xsl:copy-of select="exsl:node-set($replacesList/*/*)"/>
                </xsl:with-param>
            </xsl:call-template>
        </descripcionMetodoDePago>
    </xsl:template>
    <xsl:template name="replaces">
        <xsl:param name="text"/>
        <xsl:param name="replaces"/>
        <xsl:if test="$text!=''">
            <xsl:variable name="replace" select="$replaces/*[starts-with($text, old)][1]"/>
            <xsl:choose>
                <xsl:when test="not($replace)">
                    <xsl:copy-of select="substring($text,1,1)"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy-of select="$replace/new/node()"/>
                </xsl:otherwise>
            </xsl:choose>
            <xsl:call-template name="replaces">
                <xsl:with-param name="text" select=
                "substring($text, 1 + not($replace) + string-length($replace/old/node()))"/>
                <xsl:with-param name="replaces" select="$replaces"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

我修改了Dimitrie的答案,将他的解决方案放在模板中,并使用exsl扩展。请检查,可能对某人有用

<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:exsl="http://exslt.org/common"
    xmlns:a="http://www.tralix.com/cfd/2" 
    extension-element-prefixes="exsl">
    <xsl:output indent="yes"/>
    <xsl:template match="/*">
        <xsl:variable name="replacesList">
            <replaces>
                <replace><old>01</old><new>01 - Efectivo</new></replace>
                <replace><old>02</old><new>02 - Cheque nominativo</new></replace>
                <replace><old>03</old><new>03 - Transferencia electrónica de fondos</new></replace>
                <replace><old>04</old><new>04 - Tarjeta de Crédito</new></replace>
                <replace><old>05</old><new>05 - Monedero Electrónico</new></replace>
                <replace><old>06</old><new>06 - Dinero electrónico</new></replace>
                <replace><old>08</old><new>08 - Vales de despensa</new></replace>
                <replace><old>28</old><new>28 - Tarjeta de Débito</new></replace>
                <replace><old>29</old><new>29 - Tarjeta de Servicio</new></replace>
                <replace><old>99</old><new>99 - Otros</new></replace>
            </replaces>
        </xsl:variable>     
        <descripcionMetodoDePago>
            <xsl:call-template name="replaces">
                <xsl:with-param name="text" select="text"/>
                <xsl:with-param name="replaces">
                    <xsl:copy-of select="exsl:node-set($replacesList/*/*)"/>
                </xsl:with-param>
            </xsl:call-template>
        </descripcionMetodoDePago>
    </xsl:template>
    <xsl:template name="replaces">
        <xsl:param name="text"/>
        <xsl:param name="replaces"/>
        <xsl:if test="$text!=''">
            <xsl:variable name="replace" select="$replaces/*[starts-with($text, old)][1]"/>
            <xsl:choose>
                <xsl:when test="not($replace)">
                    <xsl:copy-of select="substring($text,1,1)"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy-of select="$replace/new/node()"/>
                </xsl:otherwise>
            </xsl:choose>
            <xsl:call-template name="replaces">
                <xsl:with-param name="text" select=
                "substring($text, 1 + not($replace) + string-length($replace/old/node()))"/>
                <xsl:with-param name="replaces" select="$replaces"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

虽然这个问题在几年前就被提出和回答了,但无论是这个答案还是很多答案都没有!我在过去几天搜索“net”时发现的其他变体能够满足我的需要:替换节点中可能包含数kb文本的多个字符串

当节点包含很少的文本时,Dimitre的版本工作得很好,但当我尝试使用它时,我几乎立刻就遇到了可怕的堆栈溢出递归调用,记住!Dimitre解决方案的问题在于,它试图将搜索模式与文本开头匹配。这意味着进行了许多递归调用,每个调用使用原始文本最右边的n-1个字符。对于1k文本,这意味着超过1000个递归调用

在对备选方案进行深入研究之后,我遇到了易卜拉欣·纳吉的一个例子,他使用更传统的前/后组合子串来执行替换。但是,对于任意数量的搜索字符串,该代码仅限于单个替换字符串

因此,我决定是时候实际动手,同时学习XSLT了!结果是下面的代码,它执行通过内部模板指定的多个字符串替换,但是可以很容易地用外部文件替换,例如,在我的测试中,到目前为止,它不会受到过度递归调用的影响

需要注意的是,替换件是ve 例如,ry basic和大多数其他现有实现一样,意味着不会尝试只匹配整个单词。我希望这些评论足以解释它的工作方式,特别是对于像我这样的XSLT初学者

现在代码

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
    xmlns:dps="dps:dps">

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

    <!--
        The original version of this code was published by Ibrahim Naji (http://thinknook.com/xslt-replace-multiple-strings-2010-09-07/).
        It works but suffered the limitation of only being able to supply a single replacement text. An alternative implementation, which
        did allow find/replace pairs to be specified, was published by Dimitre Novatchev
        (https://stackoverflow.com/questions/5213644/xslt-multiple-string-replacement-with-recursion).
        However, that implementation suffers from stack overflow problems if the node contains more than a few hundred bytes of text (and
        in my case I needed to process nodes which could include several kb of data). Hence this version which combines the best features
        of both implementations.

        John Cullen, 14 July 2017.
     -->

    <!-- IdentityTransform, copy the input to the output -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <!-- Process all text nodes. -->
    <xsl:template match="text()">
        <xsl:call-template name="string-replace-all">
            <xsl:with-param name="text" select="."/>
        </xsl:call-template>
    </xsl:template>

    <!-- Table of replacement patterns -->
    <xsl:variable name="vPatterns">
        <dps:patterns>
            <pattern>
                <old>&lt;i&gt;</old>
                <new>&lt;em&gt;</new>
            </pattern>
            <pattern>
                <old>&lt;/i&gt;</old>
                <new>&lt;/em&gt;</new>
            </pattern>
            <pattern>
                <old>&lt;b&gt;</old>
                <new>&lt;strong&gt;</new>
            </pattern>
            <pattern>
                <old>&lt;/b&gt;</old>
                <new>&lt;/strong&gt;</new>
            </pattern>
        </dps:patterns>
    </xsl:variable>

    <!--
        Convert the internal table into a node-set. This could also be done via a call to document()
        for example select="document('')/*/myns:params/*" with a suitable namespace declaration, but
        in my case that was not possible because the code is being used in with a StreamSource.
     -->
    <xsl:variable name="vPats" select="exsl:node-set($vPatterns)/dps:patterns/*"/>

    <!-- This template matches all text() nodes, and calls itself recursively to performs the actual replacements. -->
    <xsl:template name="string-replace-all">
        <xsl:param name="text"/>
        <xsl:param name="pos" select="1"/>
        <xsl:variable name="replace" select="$vPats[$pos]/old"/>
        <xsl:variable name="by" select="$vPats[$pos]/new"/>
        <xsl:choose>

            <!-- Ignore empty strings -->
            <xsl:when test="string-length(translate(normalize-space($text), ' ', '')) = 0"> 
                <xsl:value-of select="$text"/>
            </xsl:when>

            <!-- Return the unchanged text if the replacement is larger than the input (so no match possible) -->
            <xsl:when test="string-length($replace) > string-length($text)">
                <xsl:value-of select="$text"/>
            </xsl:when>

            <!-- If the current text contains the next pattern -->
            <xsl:when test="contains($text, $replace)">
                <!-- Perform a recursive call, each time replacing the next occurrence of the current pattern -->
                <xsl:call-template name="string-replace-all">
                    <xsl:with-param name="text" select="concat(substring-before($text,$replace),$by,substring-after($text,$replace))"/>
                    <xsl:with-param name="pos" select="$pos"/>
                </xsl:call-template>
            </xsl:when>

            <!-- No (more) matches found -->
            <xsl:otherwise>
                <!-- Bump the counter to pick up the next pattern we want to search for -->
                <xsl:variable name="next" select="$pos+1"/>
                <xsl:choose>
                    <!-- If we haven't finished yet, perform a recursive call to process the next pattern in the list. -->
                    <xsl:when test="boolean($vPats[$next])">
                        <xsl:call-template name="string-replace-all">
                            <xsl:with-param name="text" select="$text"/>
                            <xsl:with-param name="pos" select="$next"/>
                        </xsl:call-template>
                    </xsl:when>

                    <!-- No more patterns, we're done. Return the fully processed text. -->
                    <xsl:otherwise>
                        <xsl:value-of select="$text"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

虽然这个问题在几年前就被提出和回答了,但无论是这个答案还是很多答案都没有!我在过去几天搜索“net”时发现的其他变体能够满足我的需要:替换节点中可能包含数kb文本的多个字符串

当节点包含很少的文本时,Dimitre的版本工作得很好,但当我尝试使用它时,我几乎立刻就遇到了可怕的堆栈溢出递归调用,记住!Dimitre解决方案的问题在于,它试图将搜索模式与文本开头匹配。这意味着进行了许多递归调用,每个调用使用原始文本最右边的n-1个字符。对于1k文本,这意味着超过1000个递归调用

在对备选方案进行深入研究之后,我遇到了易卜拉欣·纳吉的一个例子,他使用更传统的前/后组合子串来执行替换。但是,对于任意数量的搜索字符串,该代码仅限于单个替换字符串

因此,我决定是时候实际动手,同时学习XSLT了!结果是下面的代码,它执行通过内部模板指定的多个字符串替换,但是可以很容易地用外部文件替换,例如,在我的测试中,到目前为止,它不会受到过度递归调用的影响

应该注意的是,替换是非常基本的,就像大多数其他现有的实现一样,这意味着没有尝试只匹配整个单词,例如。我希望这些评论足以解释它的工作方式,特别是对于像我这样的XSLT初学者

现在代码

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
    xmlns:dps="dps:dps">

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

    <!--
        The original version of this code was published by Ibrahim Naji (http://thinknook.com/xslt-replace-multiple-strings-2010-09-07/).
        It works but suffered the limitation of only being able to supply a single replacement text. An alternative implementation, which
        did allow find/replace pairs to be specified, was published by Dimitre Novatchev
        (https://stackoverflow.com/questions/5213644/xslt-multiple-string-replacement-with-recursion).
        However, that implementation suffers from stack overflow problems if the node contains more than a few hundred bytes of text (and
        in my case I needed to process nodes which could include several kb of data). Hence this version which combines the best features
        of both implementations.

        John Cullen, 14 July 2017.
     -->

    <!-- IdentityTransform, copy the input to the output -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <!-- Process all text nodes. -->
    <xsl:template match="text()">
        <xsl:call-template name="string-replace-all">
            <xsl:with-param name="text" select="."/>
        </xsl:call-template>
    </xsl:template>

    <!-- Table of replacement patterns -->
    <xsl:variable name="vPatterns">
        <dps:patterns>
            <pattern>
                <old>&lt;i&gt;</old>
                <new>&lt;em&gt;</new>
            </pattern>
            <pattern>
                <old>&lt;/i&gt;</old>
                <new>&lt;/em&gt;</new>
            </pattern>
            <pattern>
                <old>&lt;b&gt;</old>
                <new>&lt;strong&gt;</new>
            </pattern>
            <pattern>
                <old>&lt;/b&gt;</old>
                <new>&lt;/strong&gt;</new>
            </pattern>
        </dps:patterns>
    </xsl:variable>

    <!--
        Convert the internal table into a node-set. This could also be done via a call to document()
        for example select="document('')/*/myns:params/*" with a suitable namespace declaration, but
        in my case that was not possible because the code is being used in with a StreamSource.
     -->
    <xsl:variable name="vPats" select="exsl:node-set($vPatterns)/dps:patterns/*"/>

    <!-- This template matches all text() nodes, and calls itself recursively to performs the actual replacements. -->
    <xsl:template name="string-replace-all">
        <xsl:param name="text"/>
        <xsl:param name="pos" select="1"/>
        <xsl:variable name="replace" select="$vPats[$pos]/old"/>
        <xsl:variable name="by" select="$vPats[$pos]/new"/>
        <xsl:choose>

            <!-- Ignore empty strings -->
            <xsl:when test="string-length(translate(normalize-space($text), ' ', '')) = 0"> 
                <xsl:value-of select="$text"/>
            </xsl:when>

            <!-- Return the unchanged text if the replacement is larger than the input (so no match possible) -->
            <xsl:when test="string-length($replace) > string-length($text)">
                <xsl:value-of select="$text"/>
            </xsl:when>

            <!-- If the current text contains the next pattern -->
            <xsl:when test="contains($text, $replace)">
                <!-- Perform a recursive call, each time replacing the next occurrence of the current pattern -->
                <xsl:call-template name="string-replace-all">
                    <xsl:with-param name="text" select="concat(substring-before($text,$replace),$by,substring-after($text,$replace))"/>
                    <xsl:with-param name="pos" select="$pos"/>
                </xsl:call-template>
            </xsl:when>

            <!-- No (more) matches found -->
            <xsl:otherwise>
                <!-- Bump the counter to pick up the next pattern we want to search for -->
                <xsl:variable name="next" select="$pos+1"/>
                <xsl:choose>
                    <!-- If we haven't finished yet, perform a recursive call to process the next pattern in the list. -->
                    <xsl:when test="boolean($vPats[$next])">
                        <xsl:call-template name="string-replace-all">
                            <xsl:with-param name="text" select="$text"/>
                            <xsl:with-param name="pos" select="$next"/>
                        </xsl:call-template>
                    </xsl:when>

                    <!-- No more patterns, we're done. Return the fully processed text. -->
                    <xsl:otherwise>
                        <xsl:value-of select="$text"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>


请记住,XML规范化将任意字符序列xD xA转换为一个xA。同时检查我的完整参数化答案。好问题,+1。请参阅我的答案,我认为这是一个比我答案末尾的当前公认解释更好的解决方案:请记住,XML规范化将任意字符序列xD xA转换为一个xA。同时检查我的完整参数化答案。好问题,+1。请参阅我的答案,我认为这是一个比我答案末尾的当前公认解释更好的解决方案:非常感谢。。我似乎无法理解我做错了什么。@Alejandro:我认为如果不是替换字符串中包含的第一个模式,而是替换在最左边位置的模式,那就更正确了。因此,如果我们有:关系->映射和相关->相似性,并且在模式序列中关系先于相关,那么您的解决方案会生成comapping,而我的解决方案会生成相似性。@Dimitre:这是两种有效但不同的方法,我的优先,你的优先。@Alejandro:我相信在输入中以先到先得的方式进行替换是正确的。@Alejandro:我编辑了我的解决方案,现在它不仅从左到右应用替换,而且在这个过程中的任何位置,它总是替换找到的最长模式。这是最符合多个替换的实际用例的解决方案。非常感谢。。我似乎无法理解我做错了什么。@Alejandro:我认为如果不是替换字符串中包含的第一个模式,而是替换在最左边位置的模式,那就更正确了。因此,如果我们有:关系->映射和相关->相似性,并且在模式序列中关系先于相关,那么您的解决方案会生成comapping,而我的解决方案会生成相似性。@Dimitre:这是两种有效但不同的方法,我的优先,你的优先。@Alejandro:我相信在输入中以先到先得的方式进行替换是正确的。@Alejandro:我编辑了我的解决方案,现在它不仅从左到右应用替换,而且在这个过程中的任何位置,它总是替换找到的最长模式。这是最符合多个替换的实际用例的解决方案。+1这也是顺序搜索的有效方法。尽管它在名称空间声明方面仍然存在问题:实际输出是最慢的。当然,对于外部文档,这两种解决方案都不会有问题。@Alejandro:是的,我想实际的使用总是涉及到从外部设置参数,因此甚至没有费心解释这个问题。非常感谢您非常透彻的解释和示例。但我必须承认,这个ap
这种方法有点超出我的需要,但我一定会把它作为我未来的需要参考。顺便说一句,我对XSLT的研究才刚刚开始两周,我开始了解XSLT真正有多强大,特别是在与优秀导师(如Yourlf)合作时。再次感谢您花时间教那些愿意学习的人。@DimitreNovatchev这对较小的xml文件很有效,但现在我意识到它在.Net中引发堆栈溢出异常。user357812提供的解决方案似乎没有问题。@Penko,有些(如果不是所有的话)XSLT 1.0处理器不支持尾部递归,并且可能因如此长的输入而崩溃。第二个解决方案尚未崩溃,因为它的步骤不是单个字符,而是直到下一次出现的所有内容。然而,第二种解决方案不能很好地处理重叠的搜索模式(请参阅我的评论),并且在较长的输入时仍然会崩溃。可以使用DVC分而治之的方法,如中所示,但是这里的分割有点棘手。+1这也是顺序搜索的有效方法。尽管它在名称空间声明方面仍然存在问题:实际输出是最慢的。当然,对于外部文档,这两种解决方案都不会有问题。@Alejandro:是的,我想实际的使用总是涉及到从外部设置参数,因此甚至没有费心解释这个问题。非常感谢您非常透彻的解释和示例。我必须承认,虽然这种方法有点超出我的需要,但我肯定会参考我未来的需要。顺便说一句,我对XSLT的研究才刚刚开始两周,我开始了解XSLT真正有多强大,特别是在与优秀导师(如Yourlf)合作时。再次感谢您花时间教那些愿意学习的人。@DimitreNovatchev这对较小的xml文件很有效,但现在我意识到它在.Net中引发堆栈溢出异常。user357812提供的解决方案似乎没有问题。@Penko,有些(如果不是所有的话)XSLT 1.0处理器不支持尾部递归,并且可能因如此长的输入而崩溃。第二个解决方案尚未崩溃,因为它的步骤不是单个字符,而是直到下一次出现的所有内容。然而,第二种解决方案不能很好地处理重叠的搜索模式(请参阅我的评论),并且在较长的输入时仍然会崩溃。可以使用DVC分而治之的方法,如中所示,但是这里的拆分有点棘手。