XPath / XSLT: как выбрать все элементы, которые удовлетворяют условию, включающему другой набор элементов - PullRequest
2 голосов
/ 13 сентября 2011

У меня есть XML-документ, подобный следующему:

<tt>
  <a text="1"/>
  <a text="2"/>
  ...
  <a text="n"/>

  <b text="14">data</b>
  <b text="2">data</b>
  ...
</tt>

Как выбрать все <b> элементы, у которых атрибут text не равен атрибуту text любого из <a> элементов?Я использую XPath 1.0.

Я думал о чем-то вроде tt/b[not (tt/a[@text = xxx::@text])], где xxx должно относиться к исследуемому элементу tt/b.Я не знаю, как именно это можно сделать.

1 Ответ

6 голосов
/ 14 сентября 2011

Ответ, такой как /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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...