Одни и те же XPath — разные результаты

$str = '
<body>
<table><tr><td><b class="1">1</b></td></tr></table>
<table><tr><td><b class="2">1</b></td></tr></table>
<p>some text</p>
</body>';

$dom = new DOMDocument();
$dom->loadHTML($str);
$xpath = new DOMXpath($dom);

foreach($xpath->query('//table[//b[contains(@class, "2")]]') as $i) 
   print_r($i);

echo "------------------------------------------\n";

foreach($xpath->query('//table//b[contains(@class, "2")]/ancestor::table') as $i) 
   print_r($i);

Первый XPath выбирает обе таблицы, а второй получает только целевую (вторую) таблицу. Почему?

тест на eval.in


person splash58    schedule 10.02.2016    source источник


Ответы (2)


Ошибка в вашем предикате XPath [//b...]. Вместо этого должно быть [.//b...].

Объяснение: [...] — это предикаты, они действуют только как фильтры. Когда вы говорите a[b], вы выбираете все a узлы, которые удовлетворяют предикату [b]. В случае, если a и b являются элементами, из текущего узла контекста будут выбраны все элементы a, которые содержат элемент b в качестве дочернего элемента.

  • //b является AbbreviatedAbsoluteLocationPath и выбирает все узлы элементов b во всем документе. Обе таблицы находятся в документе с подходящим элементом b, поэтому предикат [//b] всегда истинен для вашего документа, независимо от того, где вы его применяете.
  • .//b является AbbreviatedRelativeLocationPath и выбирает все узлы элементов b, которые являются потомками (дочерними элементами и их дочерними элементами, рекурсивно). Предикат [.//b] будет верен только для table элементов, у которых есть элемент-потомок b.

Выражения пути шага, такие как //b или .//b, при использовании в качестве предикатов, таких как [//b] или [.//b], являются истинными, если набор узлов, выбранный выражением пути шага, не пуст.

Примененный предикат ничего не меняет, потому что //b вместо .//b: //b[contains(@class, "2")] выбирает все элементы во всем документе, которые содержат "2" в их атрибуте class. Вы в основном выполняете проверку документа, а не дерева ниже желаемого элемента table, и эта проверка документа выполняется для обоих элементов table, потому что оба находятся в документе, который содержит элемент b, у которого есть «2» в его атрибуте class .

person Christian Hujer    schedule 10.02.2016
comment
1) вы можете посмотреть демо-версию eval 2) ваше объяснение не совсем верно. В первом случае достаточно, чтобы b[содержит] в любом месте документа. Но все равно спасибо - person splash58; 10.02.2016
comment
Почему второй запрос неверен? Мне кажется правильным: сначала он выбирает все (два) table элемента в документе; для каждого из них выбирает всех b потомков (по одному на таблицу); затем для каждого из них проверяется @class атрибут; предикат оказывается истинным для одного из них; для этого элемента b выражение последнего шага возвращает его ancestor:table. Я ошибаюсь в своей интерпретации? Вот форк eval.in с измененными текстовыми узлами, чтобы различать возвращаемые table элементов. - person CiaPan; 10.02.2016
comment
@CiaPan Внутренний выбор - это просто предикаты. Путь /ancestor::table применяется к выбранным узлам элементов table. С их точки зрения, не существует элементов-предков table. С осью ancestor-or-self:: все было бы иначе. - person Christian Hujer; 10.02.2016
comment
@CiaPan Почему второй запрос неверен? второй верен - person splash58; 10.02.2016
comment
@CiaPan Насколько мне известно, этот движок XPath в указанной вами ссылке не работает. - person Christian Hujer; 10.02.2016
comment
@ChristianHujer, а как насчет этого движка - xpathtester.com/xpath/34b32896a78f9e0166dfc7808718cf63 - person splash58; 10.02.2016
comment
@splash58 Это правильно: для <doc> с двумя элементами <table>, такими как ваш, и вашим вторым XPath он правильно возвращает нулевые узлы. - person Christian Hujer; 10.02.2016
comment
@ChristianHujer «Внутренний выбор» //b во втором запросе //table//b[...]/ancestor::table не является предикатом; это шаг в выражении пути. Предикат — это [contains(...)] выражение, которое фильтрует некоторые из b элементов. - person CiaPan; 11.02.2016
comment
@CiaPan завернул b в [b], где я говорил о предикате. Любое другое место, где вы думаете, что это неясно? - person Christian Hujer; 11.02.2016
comment
@CiaPan Да, ты прав. Второе из выражений OP (//table//b[contains(@class, "2")]/ancestor::table) выберет только один из table элементов - нужный. Кристиан, тут ты ошибаешься: /ancestor::table применяется не к элементу table, а к элементу b. Второе выражение не должно возвращать два элемента. Если это так, то ваша реализация не работает. (Я думаю, вы просто перепутали //table[//b[contains(@class, "2")]]/ancestor::table с //table//b[contains(@class, "2")]/ancestor::table. - person Mathias Müller; 11.02.2016
comment
@MathiasMüller Ах да, я неправильно истолковал выражение второго OP как //table[//b[contains(@class, "2")]]/ancestor::table - и это довольно последовательно! Я обновил свой вопрос соответственно. - person Christian Hujer; 12.02.2016
comment
Вот еще один пример, в котором данные XML немного отличаются от данных OP, поэтому немного проще идентифицировать возвращаемые объекты. Можно заменить «2» на «1», прежде чем нажать «Тест», чтобы увидеть, как различаются результаты. (Я подготовил это вчера, но вырвался из Интернета и не мог опубликовать до сих пор.) - person CiaPan; 12.02.2016
comment
О, я забыл вставить ссылку. :( Вот он: xpathtester.com/xpath/8ee8119a976954d75866561a4580a7be - person CiaPan; 12.02.2016

Принятый ответ исправляет ошибку, но на самом деле не объясняет, почему исходное выражение пути пошло не так.

Ваше первое выражение выглядит так:

//table[//b[contains(@class, "2")]]

Он имеет два предиката, один из которых вложен в другой:

//table[//b[contains(@class, "2")]]
           ^---------------------^       inner predicate
       ^--------------------------^      outer predicate

Думайте о предикатах как о фильтрах, которые применяются к левому контексту предиката. В крайних случаях такой предикат отбрасывает либо ни один, либо все узлы промежуточных результатов.

Каждый узел промежуточного результата сохраняется только в том случае, если предикат справа от него оценивается как true. В случае внутреннего предиката:

//b[contains(@class, "2")]

//b дает набор промежуточных узлов элементов b (все узлы элементов b во всем документе), которые затем фильтруются предикатом [contains(@class, "2")]. Учитывая входной XML-документ, выражение внутри предиката возвращает true только для одного из b элементов.

Но //b[contains(@class, "2")], в свою очередь, служит содержанием внешнего предиката:

//table[outer predicate]

Теперь //table выбирает в качестве промежуточного результата все table узлов-элементов во всем документе, и для каждого из них проверяется выражение внутри предиката.

Важно, что внешний предикат //b[contains(@class, "2")] вернет true для обоих table элементов. Это потому, что для них обоих верно, что где-то во всем документе есть элемент b, чей атрибут class содержит 2.

Что вы на самом деле хотели сделать, так это: оценить выражение внешнего предиката с точки зрения каждого элемента table - и принятый ответ показывает, как это сделать. А именно, используя .// вместо // в предикате.

person Mathias Müller    schedule 10.02.2016
comment
Спасибо за объяснение / Я попытался сказать это во 2-м пункте моего комментария в надежде, что ответчик улучшит текст. :) - person splash58; 10.02.2016