Я пытаюсь получить все текстовые узлы / 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; }
Очевидно, вы могли бы превратить это в функцию по своему желанию. Также обратите внимание, что поскольку ваше выражение начинается с корня документа (ведущий /
), фактический контекст не имеет значения, поэтому я использовал немного другое выражение выше.