如何查找具有相同子节点的节点

时间:2011-06-22 10:27:30

标签: xml xslt xslt-1.0

我有以下xml。请注意,节点n1和n3具有相同的子节点(顺序可以不同)。如何编写XSL转换来识别此类节点?

<Document>
    <Node name="n1">
        <Item value="v1">
        <Item value="v2">
        <Item value="v3">
    </Node>
    <Node name="n2">
        <Item value="p1">
        <Item value="p2">
        <Item value="p3">
    </Node>
    <Node name="n3">
        <Item value="v3">
        <Item value="v1">
        <Item value="v2">
    </Node>
</Document>

5 个答案:

答案 0 :(得分:2)

这是尝试使用XSLT 1.0:

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

  <xsl:param name="sep" select="' '"/>

  <xsl:output indent="yes"/>

  <xsl:template match="Node">
    <Node name="{@name}">
      <xsl:attribute name="matches">
        <xsl:apply-templates 
          select="../Node[not(generate-id() = generate-id(current()))]
                         [count(Item) = count(current()/Item)]
                         [not(Item[not(@value = current()/Item/@value)])]"
           mode="check"/>
      </xsl:attribute>
    </Node>
  </xsl:template>

  <xsl:template match="Node" mode="check">
    <xsl:if test="position() &gt; 1">
      <xsl:value-of select="$sep"/>
    </xsl:if>
    <xsl:value-of select="@name"/>
  </xsl:template>

</xsl:stylesheet>

使用Saxon 6.5.5针对示例输入运行样式表

<Document>
    <Node name="n1">
        <Item value="v1"/>
        <Item value="v2"/>
        <Item value="v3"/>
    </Node>
    <Node name="n2">
        <Item value="p1"/>
        <Item value="p2"/>
        <Item value="p3"/>
    </Node>
    <Node name="n3">
        <Item value="v3"/>
        <Item value="v1"/>
        <Item value="v2"/>
    </Node>
    <Node name="n4">
        <Item value="p3"/>
        <Item value="v1"/>
        <Item value="v2"/>
    </Node>
    <Node name="n5">
        <Item value="v2"/>
        <Item value="v1"/>
        <Item value="v3"/>
    </Node> 
    <Node name="n6">
        <Item value="v2"/>
        <Item value="v1"/>
        <Item value="v3"/>
        <Item value="v4"/>
    </Node> 
    <Node name="n7">
        <Item value="v1"/>
        <Item value="v1"/>
        <Item value="v2"/>
        <Item value="v3"/>
        <Item value="v4"/>
    </Node> 
</Document>

我得到以下结果:

<Node name="n1" matches="n3 n5"/>

<Node name="n2" matches=""/>

<Node name="n3" matches="n1 n5"/>

<Node name="n4" matches=""/>

<Node name="n5" matches="n1 n3"/>

<Node name="n6" matches=""/>

<Node name="n7" matches=""/>

答案 1 :(得分:1)

[edit] XSLT 2.0样式表

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">

  <xsl:output indent="yes"/>

  <xsl:template match="Node">
    <Node name="{@name}" matches="{../Node[not(. is current())][every $item in current()/Item satisfies $item/@value = ./Item/@value]/@name}"/>
  </xsl:template>

</xsl:stylesheet>

应用于

<Document>
    <Node name="n1">
        <Item value="v1"/>
        <Item value="v2"/>
        <Item value="v3"/>
    </Node>
    <Node name="n2">
        <Item value="p1"/>
        <Item value="p2"/>
        <Item value="p3"/>
    </Node>
    <Node name="n3">
        <Item value="v3"/>
        <Item value="v1"/>
        <Item value="v2"/>
    </Node>
    <Node name="n4">
        <Item value="p3"/>
        <Item value="v1"/>
        <Item value="v2"/>
    </Node>
    <Node name="n5">
        <Item value="v2"/>
        <Item value="v1"/>
        <Item value="v3"/>
    </Node>    
</Document>

输出

<Node name="n1" matches="n3 n5"/>
<Node name="n2" matches=""/>
<Node name="n3" matches="n1 n5"/>
<Node name="n4" matches=""/>
<Node name="n5" matches="n1 n3"/>

答案 2 :(得分:1)

如果所有子项都必须匹配,那么我认为Martin的解决方案需要修改才能使用

<Node name="{@name}" 
      matches="{../Node[not(. is current())]
                        [count(Item) = count(current()/Item)]
                         [every $c in Item/@value 
                             satisfies 
                                $c = current()/Item/@value
                         ]
                          /@name
               }"/>

这可能不完全符合要求,例如,如果存在值为v1,v2,v3的另一个节点,它将匹配具有值v1,v2,v2的节点。但由于要求没有非常精确地指定,我不得不猜测一下。

(注意Martin的解决方案是XSLT 1.0,而我的是XSLT 2.0。除非人们明确说出他们需要的东西,否则我不会去编写XSLT 1.0代码。)

答案 3 :(得分:1)

这是一个完整的XSLT 1.0解决方案,它足够通用,即使允许Node让孩子拥有任何名字,它也能产生正确的结果:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kNodeBySign" match="Node" use="@signature"/>

 <xsl:template match="/*">
     <xsl:variable name="vrtfPass1">
      <xsl:apply-templates/>
     </xsl:variable>

     <xsl:apply-templates mode="pass2"
          select="ext:node-set($vrtfPass1)"/>
 </xsl:template>

 <xsl:template match="Node">
  <Node name="{@name}">
   <xsl:variable name="vSignature">
    <xsl:for-each select="*">
     <xsl:sort select="name()"/>
     <xsl:sort select="@value"/>
      <xsl:value-of select="concat(name(),'+++',@value)"/>
    </xsl:for-each>
  </xsl:variable>

  <xsl:attribute name="signature">
   <xsl:value-of select="$vSignature"/>
  </xsl:attribute>
  </Node>
 </xsl:template>

 <xsl:template match="/" mode="pass2">
  <xsl:for-each select=
    "Node[generate-id()
         =
          generate-id(key('kNodeBySign',@signature)[1])
         ]
    ">

    <Node name="{@name}">
      <xsl:variable name="vNodesInGroup">
        <xsl:for-each select=
          "key('kNodeBySign',@signature)[position()>1]">
          <xsl:value-of select="concat(@name, ' ')"/>
        </xsl:for-each>
      </xsl:variable>

      <xsl:attribute name="matches">
       <xsl:value-of select="$vNodesInGroup"/>
      </xsl:attribute>
    </Node>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

应用于此XML文档时

<Document>
    <Node name="n1">
        <Item value="v1"/>
        <Item value="v2"/>
        <Item value="v3"/>
    </Node>
    <Node name="n2">
        <Item value="p1"/>
        <Item value="p2"/>
        <Item value="p3"/>
    </Node>
    <Node name="n3">
        <Item value="v3"/>
        <Item value="v1"/>
        <Item value="v2"/>
    </Node>
    <Node name="n4">
        <Item value="p3"/>
        <Item value="v1"/>
        <Item value="v2"/>
    </Node>
    <Node name="n5">
        <Item value="v2"/>
        <Item value="v1"/>
        <Item value="v3"/>
    </Node>
    <Node name="n6">
        <Item value="v2"/>
        <Item value="v1"/>
        <Item value="v3"/>
        <Item value="v4"/>
    </Node>
    <Node name="n7">
        <Item value="v1"/>
        <Item value="v1"/>
        <Item value="v2"/>
        <Item value="v3"/>
        <Item value="v4"/>
    </Node>
</Document>

产生了想要的正确结果

<Node name="n1" matches="n3 n5 "/>
<Node name="n2" matches=""/>
<Node name="n4" matches=""/>
<Node name="n6" matches=""/>
<Node name="n7" matches=""/>

<强>解释

  1. 这是两次转换。

  2. 第一遍的结果是一个XML片段,其中包含Node个元素及其name属性和一个新添加的属性:signature。这是所有孩子的名称和值的串联(以正常,排序的形式)。在这个具体案例中,pass1的结果如下:

  3. 在第2阶段,我们使用Muenchian方法按照Node属性对所有signature元素进行分组。每个组中的第一个Node在输出中使用新属性matches表示,其值为空格分隔的其余name元素Node属性的并置连接现在的小组。

答案 4 :(得分:1)

这是我的(HUMBLE)XSLT 1.0方法。它不像其他人那么优雅,在许多情况下它可能是失败的,但是,根据这个问题,它完成了工作并且还有一定程度的定制。

节点之间的检查是针对使用命名模板构建的模式执行的:build-pattern。例如,在您的情况下,我将使用节点的所有value属性构建的模式进行比较;也就是说,将第一个节点与v1v2v3之类的模式进行比较。该模式基于名称为Item的元素构建。显然,这种模式可以根据要求进行更改。

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

    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="Node">

        <xsl:variable name="current">
            <xsl:call-template name="build-pattern">
                <xsl:with-param name="node" select="."/>
            </xsl:call-template>
        </xsl:variable>

        <xsl:copy>
            <xsl:copy-of select="@name"/>
            <xsl:attribute name="matches">

                <xsl:for-each select="../Node[not(generate-id()
                    = generate-id(current()))]">

                    <xsl:variable name="node">
                        <xsl:call-template name="build-pattern">
                            <xsl:with-param name="node" select="."/>
                        </xsl:call-template>
                    </xsl:variable>

                    <xsl:if test="$current=$node">
                        <xsl:value-of select="@name"/>
                    </xsl:if>

                </xsl:for-each>

            </xsl:attribute>
        </xsl:copy>

    </xsl:template>

    <xsl:template name="build-pattern">
        <xsl:param name="node"/>
        <xsl:for-each select="$node/Item">
            <xsl:sort select="@value"/>
            <xsl:value-of select="@value"/>
        </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>

应用于此输入时:

<Document>
    <Node name="n1">
        <Item value="v1"/>
        <Item value="v2"/>
        <Item value="v3"/>
    </Node>
    <Node name="n2">
        <Item value="p1"/>
        <Item value="p2"/>
        <Item value="p3"/>
    </Node>
    <Node name="n3">
        <Item value="v3"/>
        <Item value="v1"/>
        <Item value="v2"/>
    </Node>
    <Node name="n4">
        <Item value="p3"/>
        <Item value="v1"/>
        <Item value="v2"/>
    </Node>
    <Node name="n5">
        <Item value="v2"/>
        <Item value="v1"/>
        <Item value="v3"/>
    </Node> 
    <Node name="n6">
        <Item value="v2"/>
        <Item value="v1"/>
        <Item value="v3"/>
        <Item value="v4"/>
    </Node> 
    <Node name="n7">
        <Item value="v1"/>
        <Item value="v1"/>
        <Item value="v2"/>
        <Item value="v3"/>
        <Item value="v4"/>
    </Node> 
</Document>

产地:

<Node name="n1" matches="n3n5"></Node>
<Node name="n2" matches=""></Node>
<Node name="n3" matches="n1n5"></Node>
<Node name="n4" matches=""></Node>
<Node name="n5" matches="n1n3"></Node> 
<Node name="n6" matches=""></Node> 
<Node name="n7" matches=""></Node>

我已将上述转换推广到找到具有相同子节点的节点:

  • 任何名称
  • 以任何顺序
  • 具有任意数量的属性和任何名称

只需要用以下两个替换命名模板build-pattern

<xsl:template name="build-pattern">
    <xsl:param name="node"/>
    <xsl:for-each select="$node/*">
        <xsl:sort select="name()"/>
        <xsl:sort select="@*[1]"/>
        <xsl:value-of select="name()"/>
        <xsl:apply-templates select="attribute::*">
        </xsl:apply-templates>
    </xsl:for-each>
</xsl:template>

<xsl:template match="@*">
    <xsl:value-of select="concat(name(),.)"/>
</xsl:template>

例如,当新变换应用于以下文档时:

<Document>
    <Node name="n1">
        <Item value="v1" x="a2"/>
        <foo value="v2" x="a1"/>
        <Item value="v3"/>
    </Node>
    <Node name="n2">
        <Item value="p1"/>
        <Item value="p2"/>
        <Item value="p3"/>
    </Node>
    <Node name="n3">
        <Item value="v3"/>
        <Item value="v1" x="a2"/>
        <foo value="v2" x="a1"/>
    </Node>
    <Node name="n4">
        <Item value="v3"/>
        <Item value="v1"/>
        <xxxx value="v2"/>
    </Node>
    <Node name="n5">
        <xxxx value="v2"/>
        <Item value="v1"/>
        <Item value="v3"/>
    </Node> 
    <Node name="n6">
        <Item value="v2"/>
        <Item value="v1"/>
        <Item value="v3"/>
        <Item value="v4"/>
    </Node> 
    <Node name="n7">
        <Item value="v1"/>
        <Item value="v1"/>
        <Item value="v2"/>
        <Item value="v3"/>
        <Item value="v4"/>
    </Node> 
</Document>

产地:

<Node name="n1" matches="n3"/>
<Node name="n2" matches=""/>
<Node name="n3" matches="n1"/>
<Node name="n4" matches="n5"/>
<Node name="n5" matches="n4"/>
<Node name="n6" matches=""/>
<Node name="n7" matches=""/>