如何使用foreach循环迭代childNodes?

6
请考虑以下 PHP 代码:
<?php

 $html_data = 
 '<html><body>
  <ol>
  <li><strong>Question 1</strong> Answer1</li>
  <li><strong>Question 2</strong> Answer2</li>
  </ol></body></html>';

  $doc = new DOMDocument();
  $doc->loadHTML($html_data);
  $xpath = new DOMXPath($doc);

  $ols = $xpath->query('//ol');
  $ol = $ols->item(0);
  $lis = $ol->childNodes;

  foreach ($lis as $li) {
    echo $li->firstChild->nodeValue."<br />";
    echo $li->lastChild->nodeValue."<br />";
    //echo $li->childNodes->item(0)->nodeValue."<br />";
  }
  ?>

如果我删除这段代码最后一行的注释并访问childNodes DOM对象数组,则我的foreach循环只会执行一次。 但是,如果像上面所示使用firstChild和lastChild访问相同的元素,则可以成功迭代所有存在的'li'标签。

我完全无法理解这个问题。这是PHP中的一个错误吗?


1
你的PHP版本是多少?你能提供一个最小工作示例吗?我无法重现这个问题(请参见答案)。 - Fabian Schmengler
这是最小工作示例。http://codepad.org/cydsaaI2使用item(0)打印nodeValue,而http://codepad.org/KSjfxiBZ使用firstChild和lastChild。您可以看到使用item的代码不起作用,而另一个则起作用。当我打开错误报告时,它说我正在尝试访问非对象的成员函数。我的主要问题是为什么item(0)不起作用,而firstChild或lastChild起作用?它们不应该是相同的吗?我正在使用PHP版本5.3.9,但其他版本也有同样的问题,我想知道原因。 - Gowtham
@Gowtham:你能否编辑你的帖子,让“最小工作示例”在帖子中而不是在codepad上。这样,每当codepad决定删除你的片段时,我们就可以知道缺失链接中的内容。谢谢! - Oerd
2个回答

1

我尝试使用以下代码(在PHP 5.3.14上)重现您的问题:

Interactive shell

php > $xml = <<<XML
<<< > <root>
<<< > <ol>
<<< > <li><strong>Question 1</strong> Answer1</li>
<<< > <li><strong>Question 2</strong> Answer2</li>
<<< > </ol>
<<< > </root>
<<< > XML;
php > $doc = new DOMDocument();
php > $doc->loadXML($xml);
php > $xpath = new DOMXPath($doc);
php > $ols = $xpath->query('//ol');
php > $ol = $ols->item(0);
php > $lis = $xpath->query('//li', $ol);
php > foreach ($lis as $li) {
php { echo $li->firstChild->nodeValue."<br />";
php { echo $li->lastChild->nodeValue."<br />";
php { echo $li->childNodes->item(0)->nodeValue."<br />";
php { }
Question 1<br /> Answer1<br />
Question 1<br />
Question 2<br /> Answer2<br />
Question 2<br />

正如您所看到的,我没有成功,但一切都运行良好。我唯一改变的是将$lis = $ol->childNodes;更改为$lis = $xpath->query('//li', $ol);,因为否则在<li>节点之间会有空格文本节点,导致脚本崩溃。

请查看我的评论以获取有关问题的更多信息。另外,$lis = $ol->childNodes; 和 $lis = $xpath->query('//li', $ol); 在功能上是等效的,因此它们不会影响代码的其他部分。 :) - Gowtham
1
实际上,确实如此:http://codepad.org/OjzGoYet - 所以这似乎是childNodes访问器的副作用(可能类似于共享数组指针?肯定是一个bug!) - Fabian Schmengler
我刚刚提交了一个缺陷报告。你可以在https://bugs.php.net/bug.php?id=64104找到它。 - Gowtham
他们刚刚关闭了它 - 我提到的空格节点也是你的问题。你只是压制了错误(如果我看到了error_reporting(0),否则我早就猜到了...) - Fabian Schmengler

1
如果您不抑制错误报告,您会发现您有一个致命错误,会破坏您的脚本。
为了使用“item”方法:
foreach ($lis as $li) {
  if (method_exists($li->childNodes, 'item')) {
    echo $li->childNodes->item(0)->nodeValue."<br />";
    // To reproduce the exact output you need this line also. 
    // You need to display the second child (Answer)
    echo $li->childNodes->item(1)->nodeValue."<br />";
  }  
}

唯一的区别是第一个脚本。
foreach ($lis as $li) {
  echo $li->firstChild->nodeValue."<br />";
  echo $li->lastChild->nodeValue."<br />";    
  //echo $li->childNodes->item(0)->nodeValue."<br />";
}

只会抛出注意:试图获取非对象属性,但脚本会继续执行。

与item()方法一样,它会抛出致命错误。(致命错误:调用非对象的成员函数item()),这会导致您的脚本停止运行。

有关如何在这些nodesList上进行迭代(foreach vs. for)的详细信息,请阅读这些页面的注释。

由于<li>标签后面有空格,因此您特别遇到了这个问题。

循环如下:首先是<li>标签,然后是空格' ' DOMText元素,然后是第二个<li>标签,然后是第二个' ' DOMText元素。

在DOMText元素上它会崩溃。您可以清除空格以使其正常工作。

$html_data = '<html><body><ol><li><strong>Question 1</strong> Answer1</li><li><strong>Question 2</strong> Answer2</li></ol></body></html>';

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接