我一直试图用递归执行多个(不同的)字符串替换,并且遇到了障碍。我成功地获得了第一个更换工作,但随后的更换从未开火。我知道这与递归以及with-param字符串如何传递回调用模板有关。我看到了我的错误和为什么下一个xsl:从不触发,但我似乎无法弄清楚如何从第一个xsl传递完整的修改后的字符串:何时到第二个xsl:when。非常感谢任何帮助。
<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>
答案 0 :(得分:4)
此转换已完全参数化,不需要使用默认命名空间的任何技巧:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="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="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>
应用于此XML文档时:
<t>The quick
brown fox</t>
产生了想要的正确结果:
The slow<br/>white elephant
<强>解释强>:
从左到右和任何位置扫描文本,如果剩余的字符串以其中一个指定的模式开始,则起始子字符串将替换为为firat匹配模式指定的替换。
请注意:如果我们有搜索模式:
"relation" --> "mapping"
"corelation" --> "similarity"
按上述顺序和文字:
"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>
因此,如果我们有两个代表,如“核心” - &gt; “内核”和“核心化” - &gt; “相似性”,第二个用于包含单词“corelation”的文本,无论代表如何排序。
答案 1 :(得分:2)
此样式表显示了一个详细的解决方案,仅供您学习模式:
<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>
编辑:完整参数化解决方案。
<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>
注意:在XML 1.0中使用内联数据时出现问题:您无法像在XML 1.1中那样重置前缀命名空间声明。解决方案是使用不常见但有效的表示法:将XSLT名称空间声明为默认名称空间。
答案 2 :(得分:0)
问题可能源于换行编码的差异,导致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>
答案 3 :(得分:0)
我修改了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>
答案 4 :(得分:0)
虽然这个问题在几年前被提出(并得到了回答),但在过去几天搜索网络时我找到的这个答案和(很多!)其他变种都没有能够做我需要的:替换多个节点中的字符串,可能包含多个 kb 的文本。
当节点包含非常少的文本时,Dimitre的版本运行良好,但是当我尝试使用它时,我几乎立即违反了可怕的堆栈溢出(递归调用,请记住!)Dimitre解决方案的问题是它试图匹配搜索模式到文本的开头。这意味着进行了许多(递归)调用,每次调用使用原始文本的最右边的n-1个字符。对于1k文本,这意味着超过1000个递归调用!在挖掘替代方案之后,我遇到了Ibrahim Naji(http://thinknook.com/xslt-replace-multiple-strings-2010-09-07/)的一个例子,它使用更传统的substring-before / substring-after组合来执行替换。但是,该代码仅限于任意数量的搜索字符串的单个替换字符串。
因此,我决定实际让我的手弄脏(并同时学习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>