我想询问是否有一个函数可以用来删除由|分隔的字符串中的重复值最简单的方法。我有以下字符串的例子
1111-1|1111-1|1111-3|1111-4|1111-5|1111-3
我期待的输出是:
1111-1|1111-3|1111-4|1111-5
提前致谢。
答案 0 :(得分:2)
要在纯XSLT 1.0中执行此操作,不使用扩展函数,您需要使用递归命名模板:
<xsl:template name="distinct-values-from-list">
<xsl:param name="list"/>
<xsl:param name="delimiter" select="'|'"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<xsl:variable name="token" select="substring-before($list, $delimiter)" />
<xsl:variable name="next-list" select="substring-after($list, $delimiter)" />
<!-- output token if it is unique -->
<xsl:if test="not(contains(concat($delimiter, $next-list, $delimiter), concat($delimiter, $token, $delimiter)))">
<xsl:value-of select="concat($token, $delimiter)"/>
</xsl:if>
<!-- recursive call -->
<xsl:call-template name="distinct-values-from-list">
<xsl:with-param name="list" select="$next-list"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$list"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
完整演示:http://xsltransform.net/ncdD7mM
上述方法输出列表中每个值的 last 次出现,因为这是删除重复项的最简单方法。
这样做的副作用是不保留值的原始顺序。或者 - 更准确地说 - 它是保留的反向顺序。
我不认为在这里保留原始的远期订单是至关重要的。但是如果你确实需要它,它可以这样做(我认为比建议的替代方案更容易遵循):
<xsl:template name="distinct-values-from-list">
<xsl:param name="list"/>
<xsl:param name="delimiter" select="'|'"/>
<xsl:param name="result"/>
<xsl:choose>
<xsl:when test="$list">
<xsl:variable name="token" select="substring-before(concat($list, $delimiter), $delimiter)" />
<!-- recursive call -->
<xsl:call-template name="distinct-values-from-list">
<xsl:with-param name="list" select="substring-after($list, $delimiter)"/>
<xsl:with-param name="result">
<xsl:value-of select="$result"/>
<!-- add token if this is its first occurrence -->
<xsl:if test="not(contains(concat($delimiter, $result, $delimiter), concat($delimiter, $token, $delimiter)))">
<xsl:value-of select="concat($delimiter, $token)"/>
</xsl:if>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($result, 2)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
答案 1 :(得分:2)
到目前为止,所有呈现的XSLT 1.0解决方案都会产生错误的结果:
1111-1|1111-4|1111-5|1111-3
而想要的,正确的结果是:
1111-1|1111-3|1111-4|1111-5
现在,进行以下转换(无扩展名,纯 XSLT 1.0):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="text()" name="distinctSubstrings">
<xsl:param name="pText" select="."/>
<xsl:param name="poutDelim"/>
<xsl:param name="pFoundDistinctSubs" select="'|'"/>
<xsl:param name="pCountDistinct" select="0"/>
<xsl:if test="$pText">
<xsl:variable name="vnextSub" select="substring-before(concat($pText, '|'), '|')"/>
<xsl:variable name="vIsNewDistinct" select=
"not(contains(concat($pFoundDistinctSubs, '|'), concat('|', $vnextSub, '|')))"/>
<xsl:variable name="vnextDistinct" select=
"substring(concat($poutDelim,$vnextSub), 1 div $vIsNewDistinct)"/>
<xsl:value-of select="$vnextDistinct"/>
<xsl:variable name="vNewFoundDistinctSubs"
select="concat($pFoundDistinctSubs, $vnextDistinct)"/>
<xsl:variable name="vnextOutDelim"
select="substring('|', 2 - ($pCountDistinct > 0))"/>
<xsl:call-template name="distinctSubstrings">
<xsl:with-param name="pText" select="substring-after($pText, '|')"/>
<xsl:with-param name="pFoundDistinctSubs" select="$vNewFoundDistinctSubs"/>
<xsl:with-param name="pCountDistinct" select="$pCountDistinct + $vIsNewDistinct"/>
<xsl:with-param name="poutDelim" select="$vnextOutDelim"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
应用于此XML文档(字符串值为问题中提供的字符串):
<t>1111-1|1111-1|1111-3|1111-4|1111-5|1111-3</t>
生成想要的正确结果:
1111-1|1111-3|1111-4|1111-5
<强>解释强>:
所有找到的不同子字符串在参数$pFoundDistinctSubs
中连接 - 每当我们从分隔输入中获取下一个子字符串时,我们将它与在此传递的不同子字符串进行比较参数。这样可以确保输出第一个不同的子字符串 - 而不是其他两个解决方案中的最后一个。
我们使用无条件值确定,基于XSLT 1.0隐式将布尔false()
转换为0
和true()
至{{ 1}}只要在需要数值的上下文中使用它。特别是,1
相当于substring($x, 1 div true())
:substring($x, 1 div 1)
,这是整个字符串substring($x, 1)
。另一方面,$x
相当于substring($x, 1 div false())
- 即substring($x, 1 div 0)
,这是空字符串。
要知道为什么要避免条件非常重要:观看这个Pluralsight课程:
Tactical Design Patterns in .NET: Control Flow , Zoran Horvat答案 2 :(得分:1)
假设您可以使用XSLT 2.0,并假设输入看起来像
<?xml version="1.0" encoding="UTF-8"?>
<root>1111-1|1111-1|1111-3|1111-4|1111-5|1111-3</root>
您可以使用distinct-values
和tokenize
函数:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/root">
<result>
<xsl:value-of separator="|" select="distinct-values(tokenize(.,'\|'))"/>
</result>
</xsl:template>
</xsl:transform>
结果将是
<?xml version="1.0" encoding="UTF-8"?>
<result>1111-1|1111-3|1111-4|1111-5</result>
答案 3 :(得分:1)
我在(XSLT 1.0 How to get distinct values)
下面修改了一个样式表<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="/">
<output>
<xsl:call-template name="distinctvalues">
<xsl:with-param name="values" select="root"/>
</xsl:call-template>
</output>
</xsl:template>
<xsl:template name="distinctvalues">
<xsl:param name="values"/>
<xsl:variable name="firstvalue" select="substring-before($values, '|')"/>
<xsl:variable name="restofvalue" select="substring-after($values, '|')"/>
<xsl:if test="not(contains($values, '|'))">
<xsl:value-of select="$values"/>
</xsl:if>
<xsl:if test="contains($restofvalue, $firstvalue) = false">
<xsl:value-of select="$firstvalue"/>
<xsl:text>|</xsl:text>
</xsl:if>
<xsl:if test="$restofvalue != ''">
<xsl:call-template name="distinctvalues">
<xsl:with-param name="values" select="$restofvalue" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
示例输入:
<root>1111-1|1111-1|1111-3|1111-4|1111-5|1111-3</root>
,输出
<output>1111-1|1111-4|1111-5|1111-3</output>
**** 编辑 ****
根据迈克尔的评论,下面是使用撒克逊扩展的修订样式表:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:saxon="http://icl.com/saxon"
exclude-result-prefixes="saxon"
version="1.1">
<xsl:output omit-xml-declaration="yes"/>
<xsl:variable name="aaa">
<xsl:call-template name="tokenizeString">
<xsl:with-param name="list" select="root"/>
<xsl:with-param name="delimiter" select="'|'"/>
</xsl:call-template>
</xsl:variable>
<xsl:template match="/">
<xsl:for-each select="saxon:node-set($aaa)/token[not(preceding::token/. = .)]">
<xsl:if test="position() > 1">
<xsl:text>|</xsl:text>
</xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template name="tokenizeString">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<token>
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="substring-before($list,$delimiter)"/>
</token>
<xsl:call-template name="tokenizeString">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$list = ''">
<xsl:text/>
</xsl:when>
<xsl:otherwise>
<token>
<xsl:value-of select="$list"/>
</token>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
给出输入:
<root>cat|cat|catalog|catalog|red|red|wired|wired</root>
输出
cat|catalog|red|wired
并使用此输入:
<root>1111-1|1111-1|1111-3|1111-4|1111-5|1111-3</root>
输出
1111-1|1111-3|1111-4|1111-5