带有text()和SimpleXMLElement->xpath的php xpath不符合xpath的预期结果


php xpath with text() and SimpleXMLElement->xpath not in line with xpath expected results

我正在尝试获取/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;
}

给我:

节点值:193120

节点值: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;
    }

给:

文本节点:193

文本节点:120

SimpleXMLElement 只能表示元素和属性,无论是单独表示还是相同类型的同级元素和属性的集合。->xpath() 方法返回一个 SimpleXMLElement 对象的数组,这允许它们是非同级,但不允许任何其他节点类型。

因此,表达式/td/span/text()匹配两个文本节点,但将它们作为表示其父元素的对象返回,在本例中,父元素恰好是同一个<span>元素,从而为您提供一个包含两次相同对象的数组。

难题的其余部分是,当您将 SimpleXML 元素转换为字符串时,它会将其所有直接后代文本和 CDATA 节点组合到一个字符串中,因此193120卡在一起。

因此输出是193120,两次。

(这绝对是不直观的行为,尽管很难知道在这种情况下 SimpleXML 应该做什么;如果 XPath 表达式解析为元素或属性以外的内容,也许最好产生错误)。


由于 DOM API 为可能存在在 XML 中的每种节点提供了对象,并且 PHP 包含该 API 的完整实现,因此 XPath 表达式将按预期工作。更重要的是,SimpleXML 和 DOM 对象实际上都是围绕相同内部内存结构的包装器,因此您可以使用 dom_import_simplexml()simplexml_import_dom() 编写组合两者的操作。

举一个稍微不优雅的例子,如果要在已经使用 SimpleXML 遍历到的元素的上下文中运行 XPath 表达式,可以执行以下操作:

$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;
}

显然,您可以根据需要将其包装成一个函数。另请注意,由于您的表达式从文档根目录(前导/)开始,因此实际上下文无关紧要,这就是为什么我在上面使用了略有不同的表达式。