Ответ, такой как /tt/b[@text != ../a/@text]
, является неправильным и выбирает неправильный набор узлов :
<b text="14">data</b>
<b text="2">data</b>
Как мы видим, атрибут text
второго выбранного узла равен 2
, а равен элементу a
, атрибут text
которого равен 2
.
Вот правильное выражение XPath :
/tt/b[not(@text = ../a/@text)]
При сравнении с предоставленным XML-документом :
<tt>
<a text="1"/>
<a text="2"/>
...
<a text="n"/>
<b text="14">data</b>
<b text="2">data</b>
...
</tt>
он правильно выбирает только один узел :
<b text="14">data</b>
Объяснение
По определению оператор XPath !=
ведет себя очень не интуитивно, когда хотя бы один из его аргументов является набором узлов:
Из Рекомендации W3C XPath 1.0 :
"Если один объект для сравнения является набором узлов, а другой - числом,
тогда сравнение будет истинным, если и только если в
установить узел так, чтобы результат выполнения сравнения на
число для сравнения и по результату преобразования строкового значения
этого узла на число с помощью функции числа является истиной. Если один
объект для сравнения является набором узлов, а другой является строкой, то
сравнение будет верным, если и только если в
установить узел так, чтобы результат выполнения сравнения на
строковое значение узла, а другая строка истинна "
В данном конкретном случае для элемента:
<b text="2">data</b>
Сравнение:
@text != ../a/@text
равно true()
, хотя существует:
<a text="2"/>
, поскольку существует хотя бы один элемент ../a
(и на самом деле более одного), строковое (или числовое) значение, атрибут которого text
не равен "2"
.
Это общеизвестный факт и часто задаваемые вопросы : Всегда избегайте использования оператора !=
, если только вы абсолютно не знаете, что делаете!
Правильное решение этой проблемы - использовать функцию not()
, подобную этой :
not(@text = ../a/@text)
Это выражение оценивается как true()
только в том случае, если @text = ../a/@text
равно false()
- то есть, только если нет даже одного ../a/@text
, строковое значение которого равно строковому значению атрибута text
контекстного узла.
XSLT-проверка :
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:copy-of select="/tt/b[not(@text = ../a/@text)]"/>
</xsl:template>
</xsl:stylesheet>
когда это преобразование применяется к предоставленному XML-документу (выше), получается правильный результат :
<b text="14">data</b>