从xsl输出中删除重复节点

时间:2011-01-11 11:36:32

标签: xml xslt duplicates grouping distinct

给出以下Xml:

<record>
    <category>Sport/Racket Sports/Tennis</category>
    <category>Sport/Racket Sports/Badminton</category>
</record>

我正在尝试拆分类别,以便生成以下Xml:

<add>
   <doc>
      <field name="category_0">Sport</field>
      <field name="category_1">Sport/Racket Sports</field>
      <field name="category_2">Sport/Racket Sports/Tennis</field>
      <field name="category_2">Sport/Racket Sports/Badminton</field>
   </doc>
</add>

我已经设法生产了几乎就在那里的东西..我现在需要一种方法来删除重复的东西?有什么想法吗?

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output indent="yes"/>
    <xsl:template match="record">
        <add>
            <doc>
                <xsl:for-each select="category[. != '']">
                    <xsl:call-template name="split-cats">
                        <xsl:with-param name="prefix" select="''"/>
                        <xsl:with-param name="text" select="."/>
                        <xsl:with-param name="level" select="number(0)"/>
                    </xsl:call-template>
                </xsl:for-each>
            </doc>
        </add>
    </xsl:template>

    <xsl:template name="split-cats">
        <xsl:param name="text" select="."/>
        <xsl:param name="prefix"/>
        <xsl:param name="level" select="0"/>
            <xsl:choose>
                <xsl:when test="contains($text, '/')">
                    <field>
                        <xsl:attribute name="name">
                            <xsl:text>category_</xsl:text><xsl:value-of select="$level"/>
                        </xsl:attribute>
                        <xsl:value-of select="concat($prefix, substring-before($text, '/'))"/>
                    </field>
                    <xsl:call-template name="split-cats">
                        <xsl:with-param name="prefix" select="concat($prefix, concat(substring-before($text, '/'), '/'))"/>
                        <xsl:with-param name="text" select="substring-after($text, '/')"/>
                        <xsl:with-param name="level" select="$level + 1"/>
                    </xsl:call-template>
                </xsl:when>
                <xsl:otherwise>
                    <field>
                        <xsl:attribute name="name">
                            <xsl:text>category_</xsl:text><xsl:value-of select="$level"/>
                        </xsl:attribute>
                        <xsl:value-of select="concat($prefix, $text)"/>
                    </field>
                </xsl:otherwise>
            </xsl:choose>        
    </xsl:template>



</xsl:stylesheet>

此模板生成:

<add>
   <doc>
      <field name="category_0">Sport</field>
      <field name="category_1">Sport/Racket Sports</field>
      <field name="category_2">Sport/Racket Sports/Tennis</field>
      <field name="category_0">Sport</field>
      <field name="category_1">Sport/Racket Sports</field>
      <field name="category_2">Sport/Racket Sports/Badminton</field>
   </doc>
</add>

您可以看到其中有SportSport/Racket Sports两次:(

仅供参考:我需要能够使用XSLT 1.0

执行此操作

由于

戴夫

3 个答案:

答案 0 :(得分:1)

这是一个直接的方法,只需在代码创建的结果上使用Muenchian分组插入第二个转换步骤:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
  xmlns:exsl="http://exslt.org/common"
  exclude-result-prefixes="exsl">
    <xsl:output indent="yes"/>

    <xsl:key name="k1" match="cats/field" use="."/>

    <xsl:template match="record">
        <xsl:variable name="cats">
          <cats>
                <xsl:for-each select="category[. != '']">
                    <xsl:call-template name="split-cats">
                        <xsl:with-param name="prefix" select="''"/>
                        <xsl:with-param name="text" select="."/>
                        <xsl:with-param name="level" select="0"/>
                    </xsl:call-template>
                </xsl:for-each>
          </cats>
        </xsl:variable>
        <add>
            <doc>
              <xsl:copy-of select="exsl:node-set($cats)/cats/field[generate-id() = generate-id(key('k1', .)[1])]"/>
            </doc>
        </add>
    </xsl:template>

    <xsl:template name="split-cats">
        <xsl:param name="text" select="."/>
        <xsl:param name="prefix"/>
        <xsl:param name="level" select="0"/>
            <xsl:choose>
                <xsl:when test="contains($text, '/')">
                    <field>
                        <xsl:attribute name="name">
                            <xsl:text>category_</xsl:text><xsl:value-of select="$level"/>
                        </xsl:attribute>
                        <xsl:value-of select="concat($prefix, substring-before($text, '/'))"/>
                    </field>
                    <xsl:call-template name="split-cats">
                        <xsl:with-param name="prefix" select="concat($prefix, concat(substring-before($text, '/'), '/'))"/>
                        <xsl:with-param name="text" select="substring-after($text, '/')"/>
                        <xsl:with-param name="level" select="$level + 1"/>
                    </xsl:call-template>
                </xsl:when>
                <xsl:otherwise>
                    <field>
                        <xsl:attribute name="name">
                            <xsl:text>category_</xsl:text><xsl:value-of select="$level"/>
                        </xsl:attribute>
                        <xsl:value-of select="concat($prefix, $text)"/>
                    </field>
                </xsl:otherwise>
            </xsl:choose>        
    </xsl:template>



</xsl:stylesheet>

使用exsl:node-set但是,如果您在浏览器中定位XSLT 1.0,那么对于IE / MSXML,您需要使用http://dpcarlisle.blogspot.com/2007/05/exslt-node-set-function.html中显示的脚本来修复它。

答案 1 :(得分:1)

在不使用扩展功能的情况下实现此目的的另一种方法(但不一定能像使用Muenchian分组一样高效)是添加一个检查来比较以前的类别记录,看看它们是否以您即将使用的字符串开头

<xsl:if test="not(/record/category
              [. != '']
              [position() &lt; $pos]
              [substring(., 1, string-length($field-text)) = $field-text])">

在这段代码中 $ pos 是一个参数,包含您当前正在匹配的当前类别元素的位置,以及 $ field-text 是一个包含您要输出的文本的变量。

这是完整的XSLT样式表,它也应该为您提供所需的输出

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
   <xsl:output indent="yes"/>

   <xsl:template match="record">
      <add>
         <doc>
            <xsl:for-each select="category[. != '']">
               <xsl:call-template name="split-cats">
                  <xsl:with-param name="prefix" select="''"/>
                  <xsl:with-param name="text" select="."/>
                  <xsl:with-param name="level" select="number(0)"/>
                  <!-- Position of the current category -->
                  <xsl:with-param name="pos" select="position()"/>
               </xsl:call-template>
            </xsl:for-each>
         </doc>
      </add>
   </xsl:template>

   <xsl:template name="split-cats">
      <xsl:param name="text" select="."/>
      <xsl:param name="prefix"/>
      <xsl:param name="level" select="0"/>
      <xsl:param name="pos"/>
      <xsl:choose>
         <xsl:when test="contains($text, '/')">
            <xsl:variable name="field-text" select="concat($prefix, substring-before($text, '/'))"/>
            <!-- Test no previous category begins with the text we are about to output -->
            <xsl:if test="not(/record/category
                             [. != '']
                             [position() &lt; $pos]
                             [substring(., 1, string-length($field-text)) = $field-text])">
               <field>
                  <xsl:attribute name="name">
                     <xsl:text>category_</xsl:text>
                     <xsl:value-of select="$level"/>
                  </xsl:attribute>
                  <xsl:value-of select="$field-text"/>
               </field>
            </xsl:if>
            <xsl:call-template name="split-cats">
               <xsl:with-param name="prefix" select="concat($prefix, concat(substring-before($text, '/'), '/'))"/>
               <xsl:with-param name="text" select="substring-after($text, '/')"/>
               <xsl:with-param name="level" select="$level + 1"/>
               <xsl:with-param name="pos" select="$pos"/>
            </xsl:call-template>
         </xsl:when>
         <xsl:otherwise>
            <xsl:variable name="field-text" select="concat($prefix, $text)"/>
            <!-- Test no previous category begins with the text we are about to output -->
            <xsl:if test="not(/record/category
                             [. != '']
                             [position() &lt; $pos]
                             [substring(., 1, string-length($field-text)) = $field-text])">
               <field>
                  <xsl:attribute name="name">
                     <xsl:text>category_</xsl:text>
                     <xsl:value-of select="$level"/>
                  </xsl:attribute>
                  <xsl:value-of select="$field-text"/>
               </field>
            </xsl:if>
         </xsl:otherwise>
      </xsl:choose>
   </xsl:template>
</xsl:stylesheet>

答案 2 :(得分:1)

没有扩展函数,这个样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:strip-space elements="*"/>
    <xsl:template match="record">
        <add>
            <doc>
                <xsl:apply-templates select="category[1]"/>
            </doc>
        </add>
    </xsl:template>
    <xsl:template match="category" name="category">
        <xsl:param name="pOutput"/>
        <xsl:param name="pPrefix"/>
        <xsl:param name="pLevel" select="0"/>
        <xsl:param name="pSequence" select="concat(.,'/')"/>
        <xsl:choose>
            <xsl:when test="$pSequence">
                <xsl:variable name="vItem"
                              select="concat($pPrefix,
                                             substring-before($pSequence,
                                                              '/'))"/>
                <xsl:variable name="vOutput"
                              select="concat('|',$vItem,'|')"/>
                <xsl:if test="not(contains($pOutput,$vOutput))">
                    <field name="category_{$pLevel}">
                        <xsl:value-of select="$vItem"/>
                    </field>
                </xsl:if>
                <xsl:call-template name="category">
                    <xsl:with-param name="pOutput"
                                    select="concat($pOutput,$vOutput)"/>
                    <xsl:with-param name="pPrefix" 
                                    select="concat($vItem,'/')"/>
                    <xsl:with-param name="pLevel" select="$pLevel + 1"/>
                    <xsl:with-param name="pSequence"
                                    select="substring-after($pSequence,'/')"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates select="following-sibling::category[1]">
                    <xsl:with-param name="pOutput" select="$pOutput"/>
                </xsl:apply-templates>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

输出:

<add>
    <doc>
        <field name="category_0">Sport</field>
        <field name="category_1">Sport/Racket Sports</field>
        <field name="category_2">Sport/Racket Sports/Tennis</field>
        <field name="category_2">Sport/Racket Sports/Badminton</field>
    </doc>
</add>