Есть ли способ использовать Xpath для разбора текста между двумя тегами SETS ? Например, см. Пример:
<div class="par"> <p class="pp"> <span class="dv">1 </span>Blah blah blah blah. <span class="dv">2 </span> Yada yada yada yada. <span class="dv">3 </span>Foo foo foo foo. </p> </div> <div class="par"> <p class="pp"> <span class="dv">4 </span>Hmm hmm hmm hmm. </p> </div>
Я хочу разобрать, чтобы получить массив, как показано ниже, путем получения текста между наборами тегов SPAN:
array[0] = "Blah blah blah blah."; array[1] = "Yada yada yada yada."; array[2] = "Foo foo foo foo."; array[3] = "Hmm hmm hmm hmm.";
Могу ли я использовать DOMDocument для этого? Если нет, то каков наилучший способ добиться этого? Обратите внимание, что в середине предложений могут быть или теги. Такие как:
...<span class="dv">5 </span>Uhh uhh <a href="www.uhh.com">uhh</a> uhh. <span class="dv">6 </span>...
ОБНОВИТЬ
Кажется, вам нужен плоский список, поэтому я добавляю этот конкретный пример, чтобы не было путаницы:
$html = '<div class="par"> <p class="pp"> <span class="dv">1 </span>Blah blah blah blah. <span class="dv">2 </span> Yada yada yada yada. <span class="dv">3 </span>Foo foo foo foo. </p> </div> <div class="par"> <p class="pp"> <span class="dv">4 </span>Hmm hmm hmm hmm. </p> </div>'; $dom = DOMDocument::loadHTML($html); $finder = new DOMXPath($dom); // select THE TEXT NODES of all p elements with the class pp // - note that means its explictly class="pp", // not that "pp" is anywhere in the class list you may need to change this up depending... // post additional questions for specific xpath help $found = $finder->query('//p[@class="pp"]/text()'); $nodes = array(); // simply transform the resulting DOMNodeList into an array // for easier consumption/manipulation foreach($found as $textNode) { $node[] = $textNode->nodeValue; } print_r($nodes);
Производит:
Array ( [0] => [1] => Blah blah blah blah. [2] => Yada yada yada yada. [3] => Foo foo foo foo. [4] => [5] => Hmm hmm hmm hmm. )
Если случай всегда такой простой, я думаю, вы могли бы просто использовать xpath для получения содержимого дочерних узлов DOMText в p.pp.
$html = '<div class="par"> <p class="pp"> <span class="dv">1 </span>Blah blah blah blah. <span class="dv">2 </span> Yada yada yada yada. <span class="dv">3 </span>Foo foo foo foo. </p> </div> <div class="par"> <p class="pp"> <span class="dv">4 </span>Hmm hmm hmm hmm. </p> </div>'; $dom = DOMDocument::loadHTML($html); $finder = new DOMXPath($dom); // select all p elements with the class pp - note that means its explictly class="pp", // not that "pp" is anywhere in the class list you may need to change this up depending... // post additional questions for specific xpath help $found = $finder->query('//p[@class="pp"]'); $nodes = array(); foreach($found as $p) { // for each p element, pull its text nodes. $textNodes = $finder->query('text()', $p); $textStr = ''; // loop over the textNodes and concat them into a single string foreach ($textNodes as $n) { $textStr .= $n->nodeValue; } // push the compiled string onto the array $nodes[] = $textStr; } print_r($nodes);
Это даст результат, например:
Array ( [0] => Blah blah blah blah. Yada yada yada yada. Foo foo foo foo. [1] => Hmm hmm hmm hmm. )
Если вам действительно нужен каждый текстовый узел отдельно, вам просто нужно изменить цикл:
foreach($found as $p) { // for each p element, pull its text nodes. $textNodes = $finder->query('text()', $p); $textArr = array(); // loop over the textNodes and concat them into a single string foreach ($textNodes as $n) { $textArr[] = $n->nodeValue; } // push the compiled string onto the array $nodes[] = $textArr; }
Что даст вам:
Array ( [0] => Array ( [0] => [1] => Blah blah blah blah. [2] => Yada yada yada yada. [3] => Foo foo foo foo. ) [1] => Array ( [0] => [1] => Hmm hmm hmm hmm. ) )
Очевидно, что, поскольку вы можете видеть, что он схватил разрывы строк, вы можете легко отфильтровать их с помощью выбранного метода фильтрации массива, если они нежелательны. Или вы можете посмотреть в настройках XPath и DOMDocument, чтобы настроить это, IIRC есть некоторые настройки, касающиеся интерпретации пробелов (или нет), которые, вероятно, позволят вам избежать этого, но это может иметь и другие последствия, если вы выполняете другую обработку на тот же экземпляр DOMDocument
.
Вы хотите, чтобы первый текстовый узел был непосредственно следующим братом после элемента span:
//span/following-sibling::text()[1]
Это 1: 1 в синтаксисе PHP:
$doc = new DOMDocument(); $doc->loadHTML($buffer, LIBXML_HTML_NOIMPLIED); $xpath = new DOMXPath($doc); $expr = '//span/following-sibling::text()[1]'; $result = $xpath->evaluate($expr);
Затем вы хотите, чтобы результирующие текстовые узлы превратились в массив строк. Я бы сказал, когда вы сделаете так, что работаете, выполните некоторую нормализацию белого пространства на нем:
$array = array_map(function(DOMText $text) { return preg_replace(['~\s+~u', '~^ | $~'], [' ', ''], $text->nodeValue); }, iterator_to_array($result));
В результате получается следующее:
[ "Blah blah blah blah.", "Yada yada yada yada.", "Foo foo foo foo.", "Hmm hmm hmm hmm." ]
Полный пример кода:
<?php /** * http://stackoverflow.com/questions/27674012/php-domdocument-get-text-between-two-sets-of-tags */ $buffer = <<<HTML <div class="par"> <p class="pp"> <span class="dv">1 </span>Blah blah blah blah. <span class="dv">2 </span> Yada yada yada yada. <span class="dv">3 </span>Foo foo foo foo. </p> </div> <div class="par"> <p class="pp"> <span class="dv">4 </span>Hmm hmm hmm hmm. </p> </div> HTML; $doc = new DOMDocument(); $doc->loadHTML($buffer, LIBXML_HTML_NOIMPLIED); $xpath = new DOMXPath($doc); $expr = '//span/following-sibling::text()[1]'; $result = $xpath->evaluate($expr); $array = array_map(function(DOMText $text) { return preg_replace(['~\s+~u', '~^ | $~'], [' ', ''], $text->nodeValue); }, iterator_to_array($result)); echo json_encode($array, JSON_PRETTY_PRINT);