2016-02-10 3 views
2
$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 выбирает обе таблицы, а второй получает только целевую (вторую) таблицу. Зачем?Те же самые XPaths - разные результаты

test on eval.in

ответ

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 ,

+0

Спасибо. был неправильным :) +1 – splash58

+0

1) вы можете посмотреть демоверсию eval 2) ваше объяснение не соответствует правильности. В первом случае его достаточно, чтобы быть b [содержит] в любом месте документа. Но спасибо любым способом – splash58

+0

Почему второй запрос не так? Мне кажется правильным: сначала он выбирает все (два) элемента 'table' в документе; для каждого из них выбираются все «b» потомки (по одному на таблицу); то для каждого из них проверяется атрибут @ @ class; предикат оказывается истинным для одного из них; для этого элемента 'b' выражение последнего шага возвращает его' ancestor: table'. Я ошибаюсь в своей интерпретации? Здесь [eval.in fork] (https://eval.in/516425) с текстовыми узлами, измененными для различения возвращаемых элементов таблицы. – CiaPan

4

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

Вашего первое выражение выглядит следующим образом:

//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 - и принятый ответ показывает, как это сделать. А именно, используя .// вместо // в предикате.

+0

Спасибо за объяснение/Я попытался сказать это во 2-м пункте моего комментария в надежде, что ответчик улучшит текст. :) – splash58