可用于省略字符串上的重复值的函数

时间:2016-01-21 08:02:49

标签: xslt xslt-1.0

我想询问是否有一个函数可以用来删除由|分隔的字符串中的重复值最简单的方法。我有以下字符串的例子

1111-1|1111-1|1111-3|1111-4|1111-5|1111-3

我期待的输出是:

1111-1|1111-3|1111-4|1111-5

提前致谢。

4 个答案:

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

<强>解释

  1. 所有找到的不同子字符串在参数$pFoundDistinctSubs 中连接 - 每当我们从分隔输入中获取下一个子字符串时,我们将它与在此传递的不同子字符串进行比较参数。这样可以确保输出第一个不同的子字符串 - 而不是其他两个解决方案中的最后一个。

  2. 我们使用无条件值确定,基于XSLT 1.0隐式将布尔false()转换为0true()至{{ 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),这是空字符串。

  3. 要知道为什么要避免条件非常重要:观看这个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-valuestokenize函数:

<?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() &gt; 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