XSL: сравнение узлов путем сравнения их дочерних узлов

Я хотел бы иметь возможность сравнивать два узла на основе значений их дочерних узлов. Проверка равенства узлов с помощью оператора = просто сравнивает строковые значения соответствующих узлов. Я хотел бы сравнить их на основе значений в их дочерних узлах.

Чтобы быть немного более конкретным, я хотел бы, чтобы <a> и <b> (ниже) были равны, потому что значения @id одинаковы для <c> элементов, которые имеют соответствующие атрибуты @type, также имеют соответствующие атрибуты @id.

<a>
    <c type="type-one" id="5675"/>
    <c type="type-two" id="3423"/>
    <c type="type-three" id="9088"/>
</a>
<b>
    <c type="type-one" id="5675"/>
    <c type="type-two" id="3423"/>
</b>

Но они будут другими:

<a>
    <c type="type-one" id="5675"/>
</a>
<b>
    <c type="type-one" id="2342"/>
</b>

Единственное решение, которое я вижу, включает в себя трудоемкое сравнение с оператором for-each, которого я хотел бы избежать, если это возможно.

Если возможно, я хотел бы придерживаться XSLT 1.0. Я использую xsltproc.


person adam.baker    schedule 19.01.2013    source источник
comment
Как a и b в первом примере могут быть равны, если a имеет дочерний элемент <c type="type-three" id="9088"/>, у которого нет соответствующего дочернего элемента b ??? Пожалуйста, отредактируйте вопрос и исправьте. Текущий пример противоречит понятию равенства.   -  person Dimitre Novatchev    schedule 20.01.2013
comment
Не существует единого определения равенства... например, узлы с идентичным внутренним текстом равны в каком-то смысле, но это не та разница, которая меня интересует. Или в реальной жизни, возможно, два человека имеют одинаковую репутацию в стеке. Перелив, но неравные зарплаты и т.д. и т.п....   -  person adam.baker    schedule 20.01.2013
comment
user1447002, Равенство строго определяется как отношение эквивалентности. Если ~ является равенством, оно должно быть 1. Рефлексивным, 2. Симметричным и 3. Транзитивным. У вас нет ни того, ни другого, и вы не определили все свойства отношения, которое вы называете равенством.   -  person Dimitre Novatchev    schedule 20.01.2013
comment
user1447002, И твоё равенство точно не транзитивно.   -  person Dimitre Novatchev    schedule 20.01.2013
comment
Почему мое определение не является рефлексивным, симметричным или транзитивным? A равно B, если все значения атрибутов совпадают для пересечения A/@* и B/@*. Это кажется мне рефлексивным, симметричным и транзитивным.   -  person adam.baker    schedule 20.01.2013
comment
Это не транзитивно. Легко построить пример, где a равно b и b равно c, но a не равно c. Оставил в качестве упражнения читателю :)   -  person Dimitre Novatchev    schedule 20.01.2013
comment
user1447002, Но так или иначе, я называю это отношение просто совпадениями и с учетом этого ответил на ваш вопрос.   -  person Dimitre Novatchev    schedule 20.01.2013
comment
Я думаю, что пример, который вы ищете, это <a><item key="one" value="foo"/><item key="two" value="one"/></a>, <b><item key="one" value="foo"/><item key="three" value="bar"/></b>, <c><item key="one" value="foo"/><item key="two" value="other"/></c>, A «равно» B, B «равно» C, но A «не равно» C. Я ожидаю штрафа от полиции формальной логики. :-)   -  person adam.baker    schedule 21.01.2013
comment
user1447002, Также ожидайте штрафа от полиции по сокращению - никаких функций расширения не требуется, и решение может быть намного проще - как показано в моем ответе.   -  person Dimitre Novatchev    schedule 21.01.2013


Ответы (2)


Во-первых, отношение "равно" не может иметь такое имя.

«Равно» означает, что отношение является отношением эквивалентности. По определению любое отношение эквивалентности ~ должно быть:

  1. Рефлексивный: x ~ x .

  2. Симметрично: если x ~ y, то y ~ x

  3. Переходный: если x ~ y и y ~ z, то x ~ z.

Вот пример, показывающий, что предлагаемое отношение "равно" не является транзитивным:

x is:

<a>
    <c type="type-one" id="5675"/>
    <c type="type-two" id="3423"/>
    <c type="type-three" id="9088"/>
</a>

y is:

<b>
    <c type="type-one" id="5675"/>
    <c type="type-two" id="3423"/>
    <c type="type-four" id="1234"/>
</b>

z is:

<b>
    <c type="type-three" id="3333"/>
    <c type="type-four" id="1234"/>
</b>

Теперь мы видим, что x ~ y и y ~ z. Однако очевидно, что это не так: x ~ z

При этом я называю отношение «совпадает», и оно расслаблено, а не «равно».

Вот решение проблемы с приведенной выше корректировкой:

Обратите внимание, что это нельзя выразить одним выражением XPath, поскольку XPath 1.0 (используемый в преобразовании XSLT 1.0) не имеет переменных диапазона.

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

 <xsl:template match="/*">
     <xsl:call-template name="matches">
       <xsl:with-param name="pElem1" select="a"/>
       <xsl:with-param name="pElem2" select="b"/>
     </xsl:call-template>
 </xsl:template>

 <xsl:template name="matches">
   <xsl:param name="pElem1" select="/.."/>
   <xsl:param name="pElem2" select="/.."/>

   <xsl:variable name="vMisMatch">
       <xsl:for-each select="$pElem1/c[@type = $pElem2/c/@type]">
        <xsl:if test=
           "$pElem2/c[@type = current()/@type and not(@id = current()/@id)]">1</xsl:if>
       </xsl:for-each>
   </xsl:variable>

   <xsl:copy-of select="not(string($vMisMatch))"/>
 </xsl:template>
</xsl:stylesheet>

Когда это преобразование применяется к следующему XML-документу:

<t>
    <a>
        <c type="type-one" id="5675"/>
        <c type="type-two" id="3423"/>
        <c type="type-three" id="9088"/>
    </a>
    <b>
        <c type="type-one" id="5675"/>
        <c type="type-two" id="3423"/>
    </b>
</t>

получен желаемый правильный результат:

true

Когда такое же преобразование применяется к этому XML-документу:

<t>
    <a>
        <c type="type-one" id="5675"/>
        <c type="type-two" id="3423"/>
        <c type="type-three" id="9088"/>
    </a>
    <b>
        <c type="type-one" id="5675"/>
        <c type="type-two" id="9876"/>
    </b>
</t>

снова получен правильный результат:

false
person Dimitre Novatchev    schedule 20.01.2013
comment
хороший ответ! Не могли бы вы также проверить мой вопрос? Я был бы очень признателен, если бы узнал больше о том, как этого можно достичь! - person ; 06.09.2016
comment
@Dex'ter, я вижу, что на твой вопрос ответили (пока я спал). Вам нужна дополнительная помощь по этому поводу? - person Dimitre Novatchev; 06.09.2016
comment
если у вас есть что добавить к этому ответу, любая дополнительная информация была бы здорово :) Спасибо - person ; 06.09.2016

Вот что я придумал. Учитывая игрушечный набор данных, подобный этому:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <a>
        <item key="x" value="123"/>
        <item key="y" value="456"/>
        <item key="z" value="789"/>
    </a>
    <b>
        <item key="x" value="123"/>
        <item key="z" value="789"/>
    </b>
</root>

Эта таблица стилей показывает, как проверить равенство, как определено в вопросе.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:set="http://exslt.org/sets"  xmlns:exsl="http://exslt.org/common" 
 extension-element-prefixes="set exsl">
<xsl:output method="text" version="1.0" encoding="UTF-8"/> 

<xsl:template match="/">
    <xsl:variable name="values-are-equal">
        <xsl:call-template name="equal">
            <xsl:with-param name="A" select="/root/a"/>
            <xsl:with-param name="B" select="/root/b"/>
        </xsl:call-template>
    </xsl:variable>
    <xsl:choose>
        <xsl:when test="$values-are-equal = 1">Equal</xsl:when>
        <xsl:otherwise>Inequal</xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template name="equal">
    <xsl:param name="A" />
    <xsl:param name="B" />
    <xsl:variable name="common-keys" select="$A/item/@key[ count(set:distinct(  . | $B/item/@key )) = count( set:distinct( $B/item/@key ) ) ]"/>
    <xsl:variable name="differences">
        <xsl:for-each select="$common-keys">
            <xsl:if test="$A/item[@key = current()]/@value != $B/item[@key = current()]/@value">
                <different/>
            </xsl:if>
        </xsl:for-each>
    </xsl:variable>
    <xsl:choose>
        <xsl:when test="count( exsl:node-set($differences)/* ) > 0">0</xsl:when>
        <xsl:otherwise>1</xsl:otherwise>
    </xsl:choose>
    </xsl:template>

</xsl:stylesheet>

При этом используются некоторые расширения, доступные в xsltproc и других процессорах.

person adam.baker    schedule 21.01.2013
comment
Нет необходимости использовать какие-либо расширения, и существует более простое/короткое решение - см. мой ответ. - person Dimitre Novatchev; 21.01.2013