Как найти узлы с одинаковыми детьми - PullRequest
3 голосов
/ 22 июня 2011

У меня есть следующий 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 ]

2 голосов
/ 22 июня 2011

Вот попытка сделать это с 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 голос
/ 22 июня 2011

Это мой (смиренный) подход 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>

Iобобщил вышеприведенное преобразование для поиска узлов с одинаковыми дочерними элементами:

  • с любым именем
  • в любом порядке
  • с любым количеством атрибутов и любым именем

Нужно только заменить именованный шаблон 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=""/>
1 голос
/ 22 июня 2011

Вот полное решение 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 мы используем метод Мюнхена для группировки всех элементов Node по их атрибуту signature. Первые Node в каждой группе представлены в выходных данных новым атрибутом matches, значением которого является конкатенация с разделением пробелами атрибутов name оставшихся элементов Node в текущей группе.

1 голос
/ 22 июня 2011

Если все дочерние элементы должны совпадать, то я думаю, что решение Мартина нужно изменить, чтобы использовать

<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, v2, если есть другой узел со значениями v1, v2, v3.Но поскольку требование не было указано очень точно, мне пришлось немного угадать.

(Обратите внимание, что решение Мартина - XSLT 1.0, а мое - XSLT 2.0. Я не собираюсь писатьКод XSLT 1.0, если люди явно не говорят, что это то, что им нужно.)

1 голос
/ 22 июня 2011

[править] Таблица стилей 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"/>
...