使用 XPATH 和 PHP DOM 选择和删除节点时出现问题


Issues selecting and deleting nodes with XPATH and PHP DOM

可能是一个愚蠢的问题,但到目前为止我无法弄清楚......

我有一个 XHTML 文档作为字符串。它在$temp到目前为止一切顺利。我想做两件事。我想选择正文中的所有元标记(它们之所以存在,是因为它们与微数据结合使用(,然后删除它们。删除微数据属性后。

    $xml=new DOMDocument();
    $xml->loadXML($temp);
    $xpath = new DOMXPath($xml);
    $attr = $xpath->query("//@itemscope|//@itemprop|//@itemtype|//@itemid|//@itemref");
    foreach ($attr as $entry)
        $entry->parentNode->removeAttribute($entry->nodeName);

这行得通。但是我无法使用 Xpath 选择任何节点。

$xpath = new DOMXPath($xml); // thought I had to update this after changing the XML
echo $xpath->query("//body")->length; // => 0
echo $xml->getElementsByTagName("body")->length; // => 1

所以问题1:如何使用Xpath选择节点。为什么这不起作用?

不过,这可以获取节点列表:

$node = $xml->getElementsByTagName("body")->item(0)->getElementsByTagName("meta");

我想删除我将使用它的节点:(类似于删除上面的属性(

foreach ($node as $entry)
{
    $entry->parentNode->removeChild($entry);
}

但节点仍然存在。

所以有问题2:如何从XML文件中删除节点。

特别是任何身体节点中任意位置的元节点。

谢谢。

更新

让我添加一个 HTML 测试用例:

$temp='<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
    <head>
        <meta charset="utf-8"/>
    </head>
    <body id="dok" itemscope="itemscope" itemtype="http://schema.org/WebPage" >
        <div><div><div><meta itemprop="dummy" content="something"/></div></div></div>
        <span><meta itemprop="dummy2" content="something2"/></span>
    </body>
</html>';

通过上述内容,xPath 尝试选择正文给我的长度为 0,我无法从正文中删除所有元标记......

更新

这适用于 loadXML(( 方法:

$xpath = new DOMXPath($xml);
$xpath->registerNamespace("x","http://www.w3.org/1999/xhtml");
echo $xpath->query("//x:body")->length;

没有命名空间的解决方案

它始终是关于根html标签中的xmlns="http://www.w3.org/1999/xhtml"命名空间。 //body选择不属于任何命名空间的任何正文标记。由于我们确实指定了一个默认命名空间,并且body是该命名空间的一部分,因此//body不会选择它。我不知道以什么名称访问已经是XHTML固有的命名空间,而无需以名称声明它,但是如果我们在创建XML之前将其剥离,一切都很好。完成后,我们可以将其重新添加。

    $temp =  str_replace('xmlns="http://www.w3.org/1999/xhtml"','',$temp);
    $xml=new DOMDocument();
    $xml->loadXML($temp);
    $xpath = new DOMXPath($xml);    
    $attr = $xpath->query("//@itemscope|//@itemprop|//@itemtype|//@itemid|//@itemref");
    foreach ($attr as $entry)
        $entry->parentNode->removeAttribute($entry->nodeName);
    $node = $xpath->query("//body//meta");
    foreach ($node as $entry)
    {
        $entry->parentNode->removeChild($entry);
    }   
    $temp=$xml->saveXML();
    $temp =  str_replace('<html','<html xmlns="http://www.w3.org/1999/xhtml"',$temp);

这样//body//meta就可以按预期工作...

这段代码为我完成了这项工作:

$temp='<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
    <head>
        <meta charset="utf-8"/>
    </head>
    <body id="dok" itemscope="itemscope" itemtype="http://schema.org/WebPage" >
        <div><div><div><meta itemprop="dummy" content="something"/></div></div></div>
        <span><meta itemprop="dummy2" content="something2"/></span>
    </body>
</html>';

$xml=new DOMDocument();
$xml->loadHtml($temp);
$xpath = new DOMXPath($xml); // thought I had to update this after changing the XML
$path = "//body//meta";
echo $xpath->query($path)->length, "'n"; # 2
foreach ($xpath->query($path) as $entry)
{
    $entry->parentNode->removeChild($entry);
}
echo $xpath->query($path)->length, "'n"; # 0

我认为两个关键点是:

  1. 将文档加载为 HTML - 我无法正确解释它,但我认为 XML 正在引入命名空间,这些应该反映在 xpath 中。但是我对命名空间不太熟悉,无法真正解释它。但是,加载为 HTML 会使查询"按预期">工作,这在技术上不是正确的期望。
  2. //body//meta - xpath必须反映主体和元元素之间可以有更多的元素。因此,bodymeta之间的//

命名空间和 XML

多亏了 Dimitri 的解释,我现在可以更好地理解我只闻到的命名空间问题,并且可以将代码更新为 loadXML(( 兼容版本(仅限修改后的行(:

$xml->loadXml($temp);
$xpath = new DOMXPath($xml);
$xpath->registerNamespace('xhtml', 'http://www.w3.org/1999/xhtml');
$path = "//xhtml:body//xhtml:meta";

这会将文档加载为 XML。然后,它使用名称 xpath 对象的 xhtml 从文档中注册命名空间 URI。

然后修改 xpath 查询以正确反映元素表达式的命名空间。