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

时间:2011-06-19 22:10:31

标签: php xpath domdocument

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

我有一个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;

没有名称空间的解决方案

它始终与根xmlns="http://www.w3.org/1999/xhtml"标记中的html命名空间有关。 //body会选择任何名称空间 NOT 的正文标记。由于我们确实指定了默认命名空间,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就像预期一样......

1 个答案:

答案 0 :(得分:2)

这段代码对我有用:

$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必须反映主体和元素元素之间可以有更多元素。因此//body之间的meta
  3. 命名空间和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。然后,它从文档中注册名称为xhtml的名称空间URI,用于xpath对象。

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