php xpath с текстом () и SimpleXMLElement-> xpath не соответствует ожидаемым результатам xpath

Я пытаюсь получить все текстовые узлы / td / span.

Я пытаюсь использовать xpath / td / span / text ()

Проблема в том, что он возвращает ВСЕ текстовые узлы для каждого текстового элемента (здесь два, «193» и «120», он возвращает «193120» дважды, вместо 193 и 120 в отдельных элементах).

Я стараюсь точно такой же xpath на любом онлайн-инструменте, он отлично работает, в php, совершенно разные результаты.

использование SimpleXMLElement

$xhtmlSnippet = '<td><span>193<span>10</span><span></span><div>66</div><span>195</span><span>.</span><span>34</span><span>242</span><span></span>120<span>64</span></span></td>'; $xml = new SimpleXMLElement($xhtmlSnippet); $xresult = $xml->xpath('/td/span/text()'); foreach($xresult as $xnode){ echo "<br /><br />NodeValue: " . $xnode; } 

Дает мне:

NodeValue: 193120

NodeValue: 193120

Вот пример его правильной работы с помощью онлайн-инструмента (все остальные онлайн-инструменты также дают ожидаемый результат):

Рабочий пример в онлайн-тесте

РЕДАКТИРОВАТЬ:

Используя DOMDocument + DOMXPath, он работает, как ожидалось:

  $dom = new DOMDocument; $dom->loadXML($xhtmlSnippet); $xpath = new DOMXPath($dom); foreach ($xpath->query('/td/span/text()) as $textNode) { echo "\n\nTextNode: " . $textNode->nodeValue; } 

дает:

TextNode: 193

TextNode: 120

Элемент SimpleXMLElement может представлять только элементы и атрибуты, индивидуально или коллекцию братьев и сестер одного и того же типа. Метод ->xpath() возвращает массив объектов SimpleXMLElement, что позволяет им быть не братьями и сестрами, но не позволяет использовать какой-либо другой тип узла.

Следовательно, выражение /td/span/text() соответствует двум текстовым узлам, но возвращает их как объекты, представляющие их родительский элемент, который в этом случае оказывается одним и тем же элементом <span> , предоставляя вам массив с одним и тем же объектом в два раза.

Оставшаяся часть головоломки заключается в том, что когда вы добавляете элемент SimpleXML в строку, он объединяет все свои прямые потоки потомков и узлы CDATA в одну строку, поэтому 193 и 120 объединяются.

Таким образом, выход составляет 193120 , дважды.

(Это, безусловно, неинтуитивное поведение, хотя трудно понять, что SimpleXML должен делать в этой ситуации: возможно, было бы лучше создать ошибку, если выражение XPath разрешит нечто иное, чем элементы или атрибуты).


Поскольку DOM API имеет объекты для каждого типа узлов, которые могут существовать в XML, а PHP включает полную реализацию этого API, выражение XPath будет работать так, как ожидалось. Более того, объекты SimpleXML и DOM на самом деле являются обертками вокруг одной и той же структуры внутренней памяти, поэтому вы можете писать операции, комбинируя их с помощью dom_import_simplexml() и simplexml_import_dom() .

В качестве слегка неэлегантного примера, если вы хотите запустить выражение XPath в контексте элемента, к которому вы уже прошли SimpleXML, вы можете сделать что-то вроде этого:

 $dom_node = dom_import_simplexml($simplexml_node); $dom_xpath = new DOMXPath($dom_node->ownerDocument); $dom_xpath_result = $dom_xpath->query('span/text()', $dom_node); foreach($dom_xpath_result as $xnode){ echo "<br /><br />NodeValue: " . $xnode->nodeValue; } 

Очевидно, вы могли бы превратить это в функцию по своему желанию. Также обратите внимание, что поскольку ваше выражение начинается с корня документа (ведущий / ), фактический контекст не имеет значения, поэтому я использовал немного другое выражение выше.