用递归替换XSLT多字符串
我一直试图用递归执行多个不同的字符串替换,但遇到了一个障碍。我已经成功地让第一个替代品开始工作,但随后的替代品从未开火。我知道这与递归以及with param字符串如何传递回调用模板有关。我看到了我的错误以及为什么下一个xsl:when从未触发,但我似乎无法准确地理解如何将完整修改的字符串从第一个xsl:when传递到第二个xsl:when。非常感谢您的帮助用递归替换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
<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:when test="contains($string, 'TXT')">
<xsl:value-of select="substring-before($string, ' TXT')" />
<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>
此样式表显示了一个详细的解决方案,供您了解模式:
<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, '
')">
<xsl:call-template name="replace">
<xsl:with-param
name="pString"
select="substring-before($pString, '
')"/>
</xsl:call-template>
<br/>
<xsl:call-template name="replace">
<xsl:with-param
name="pString"
select="substring-after($pString, '
')"/>
</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="
" 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, '
')">
<xsl:call-template name="replace">
<xsl:with-param
name="pString"
select="substring-before($pString, '
')"/>
</xsl:call-template>
<br/>
<xsl:call-template name="replace">
<xsl:with-param
name="pString"
select="substring-after($pString, '
')"/>
</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="
" 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>
</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>
</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><i></old>
<new><em></new>
</pattern>
<pattern>
<old></i></old>
<new></em></new>
</pattern>
<pattern>
<old><b></old>
<new><strong></new>
</pattern>
<pattern>
<old></b></old>
<new></strong></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><i></old>
<new><em></new>
</pattern>
<pattern>
<old></i></old>
<new></em></new>
</pattern>
<pattern>
<old><b></old>
<new><strong></new>
</pattern>
<pattern>
<old></b></old>
<new></strong></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分而治之的方法,如中所示,但是这里的拆分有点棘手。