PHP DOMDocument在两个标签集之间获取文本

时间:2014-12-28 05:53:34

标签: php parsing xpath domdocument

有没有办法使用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>...

2 个答案:

答案 0 :(得分:3)

<强>更新

似乎你确实想要一个平面列表,所以我添加这个特定的例子,所以没有混淆:

$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来获取p.pp中子DOMText节点的内容。

$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实例。

答案 1 :(得分:1)

你想要第一个文本节点,它是span元素之后的直接兄弟节点:

//span/following-sibling::text()[1]

这是PHP语法中的1:1:

$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);
相关问题