1

我需要在 XML 中找到重复元素的位置。例如,在 XML 中,如下所示:

<menu>
  <juice sugar="yes" fresh="no">
    <apple/>
    <carrot/>
  </juice>
  <juice sugar="no" fresh="no">
    <apple/>
    <carrot/>
  </juice>
  <juice sugar="no" fresh="no">
    <carrot/>
    <apple/>
  </juice>
  <juice>
    <carrot kind="village" />
    <orange/>
  <juice/>
  <juice>
    <carrot kind="village" />
    <orange/>
    <carrot kind="village" />
  </juice>
</menu>

如果我的 XML 包含与共同父元素相同的元素,我需要抛出一个异常。然而,属性也很重要,应该是平等的。

后代的顺序应该不重要。这意味着,在我的示例中,应该抛出异常

  <juice sugar="no" fresh="no">
    <apple/>
    <carrot/>
  </juice>
  <juice sugar="no" fresh="no">
    <carrot/>
    <apple/>
  </juice>

因为“果汁”的所有属性都相等,并且第二个“果汁”的子代是第一个的置换子代。

另一个应该发生异常的地方是:

  <juice>
    <carrot kind="village" />
    <orange/>
    <carrot kind="village" />
  </juice>

因为“果汁”两次包含相同的“胡萝卜”。

对于这个问题的任何提示,我将不胜感激。我应该使用 XSLT 吗?或者在 C# 中反序列化我的 XML 可能更好?

4

3 回答 3

2

我会首先通过将子元素按顺序排序,将每个元素转换为规范形式;使用 XSLT 很容易做到这一点。结果应该是,根据您的规则,当且仅当它们的规范形式根据 XPath 2.0 是 deep-equal() 时,两个元素才相等。

然后我会编写一个函数,为每个元素计算某种哈希码(以便“相等”元素具有相等的哈希码)并对该哈希码执行分组。同样,这在 XSLT 2.0 中很容易做到:唯一困难的一点是设计散列函数。我怀疑您的示例没有显示真实数据,我想在建议散列函数之前查看真实数据。

然后在每个哈希码组内,您可以使用 XSLT 2.0 的 deep-equal() 函数将组中的每个成员相互比较,以消除哈希码匹配错误的情况。

于 2013-08-04T23:18:40.490 回答
1

下面的 XSLT 2.0 解决方案恰好适用于您的数据集。如果你有更多的数据可以通过它,那将有助于测试它的稳健性。

t:\ftemp>type viktoria.xml 
<?xml version="1.0" encoding="UTF-8"?>
<menu>
  <juice sugar="yes" fresh="no">
    <apple/>
    <carrot/>
  </juice>
  <juice sugar="no" fresh="no">
    <apple/>
    <carrot/>
  </juice>
  <juice sugar="no" fresh="no">
    <carrot/>
    <apple/>
  </juice>
  <juice>
    <carrot kind="village" />
    <orange/>
  </juice>
  <juice>
    <carrot kind="village" />
    <orange/>
    <carrot kind="village" />
  </juice>
</menu>

t:\ftemp>call xslt2 viktoria.xml viktoria.xsl 
<?xml version="1.0" encoding="UTF-8"?>
<exceptions>
   <duplicates>
      <juice sugar="no" fresh="no">
         <apple/>
         <carrot/>
      </juice>
      <juice sugar="no" fresh="no">
         <carrot/>
         <apple/>
      </juice>
   </duplicates>
   <children>
      <juice>
         <carrot kind="village"/>
         <orange/>
         <carrot kind="village"/>
      </juice>
   </children>
</exceptions>

t:\ftemp>type viktoria.xsl 
<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:v="urn:X-Viktoria" exclude-result-prefixes="v xsd"
                version="2.0">

<xsl:output indent="yes"/>

<!--return true if the two elements and their attributes are the same while
    ignoring children-->
<xsl:function name="v:shallow-equal" as="xsd:boolean">
  <xsl:param name="elem1" as="element()"/>
  <xsl:param name="elem2" as="element()"/>
  <xsl:sequence select="node-name($elem1)=node-name($elem2) and
    ( every $a1 in $elem1/@* satisfies ( some $a2 in $elem2/@* satisfies 
        ( node-name($a1)=node-name($a2)  and $a1 = $a2 ) ) ) and
    ( every $a2 in $elem2/@* satisfies ( some $a1 in $elem1/@* satisfies 
        ( node-name($a1)=node-name($a2)  and $a1 = $a2 ) ) )"/>
</xsl:function>

<!--return true if two elements have the same children with the same attributes
    while ignoring the children's children-->
<xsl:function name="v:element-and-children-equal" as="xsd:boolean">
  <xsl:param name="elem1" as="element()"/>
  <xsl:param name="elem2" as="element()"/>
  <xsl:sequence
    select="v:shallow-equal($elem1,$elem2) and
            ( every $child1 in $elem1/* satisfies 
                count( $elem2/*[deep-equal(.,$child1)] )=1 ) and
            ( every $child2 in $elem2/* satisfies 
                count( $elem1/*[deep-equal(.,$child2)] )=1 )"/>
</xsl:function>

<!--produce result-->
<xsl:template match="menu">
  <exceptions>
    <duplicates>
      <!--find each element that has a sibling with same children, that is,
          there is more than one such element amongst all siblings-->
      <xsl:for-each 
        select="*[ for $this in . return
              count ( ../*[v:element-and-children-equal(.,$this)] ) > 1 ]">
       <xsl:copy-of select="."/>
      </xsl:for-each>
    </duplicates>
    <children>
      <!--find each element that has duplicate children, that is,
          there is more than one of each child amongst all children-->
      <xsl:for-each
        select="*[ some $child in * satisfies
                   count ( *[deep-equal(.,$child)] ) >1 ]">
       <xsl:copy-of select="."/>
      </xsl:for-each>
    </children>
  </exceptions>
</xsl:template>

</xsl:stylesheet>
t:\ftemp>rem Done! 
于 2013-08-05T00:31:16.960 回答
0

感谢大家的回应。我用 C# 和 XmlDocument 类解决了我的问题。幸运的是我发现,只检查具有某些属性的节点就足够了,不需要反序列化。我只是递归地检查了每个节点的后代。

于 2013-08-19T18:39:36.237 回答