PHP DOMDocument 命名空间

7
我正在编写一个脚本,用于检测网页中类似Facebook点赞按钮的使用次数。由于最好使用DOM来完成此任务,所以我决定使用PHP的DOMDocument。
然而,我遇到了一个问题,那就是对于像Facebook点赞按钮这样的元素:
<fb:like send="true" width="450" show_faces="true"></fb:like>

由于该元素在技术上具有“fb”命名空间,DOMDocument会发出警告,称此命名空间前缀未定义。然后它继续剥离前缀,因此当我到达该元素时,其标记不再是fb:like,而是like

有没有任何方法可以“预注册”命名空间?有什么建议吗?

6个回答

4

在使用xml解析器之前,您可以使用tidy来优化内容。

$tidy = new tidy();
$config = array(
    'output-xml'   => true, 
    'input-xml'    => true, 
    'add-xml-decl' => true,
);
$tidy->ParseString($htmlSoup, $config);
$tidy->cleanRepair();
echo $tidy;

1

由于这个问题从未被“解决”,所以我决定实现syndance的解决方案,为那些不喜欢解决正则表达式的人提供帮助。

// do this before you use loadHTML()    
// store any name spaced elements so we can re-add them later
$postContent = preg_replace('/<(\w+):(\w+)/', '<\1 data-namespace="\2"' , $postContent);

// once you are done using domdocument fix things up
// re-construct any name-spaced tags
$postContent = preg_replace('/<(\w+) data-namespace="(\w+)"/', '<\1:\2 ' , $postContent);

1
这是一个很好的开始,但似乎会在破折号后截断标签。例如,gcse:searchbox-resultsonly 变成了 gcse:searchbox。 - MadtownLems

0
我遇到了同样的问题,并想出了以下解决方案/变通方法:
使用DOMDocument解析具有命名空间的HTML时,没有很好的方法可以不丢失命名空间,但是有一些变通方法:
  • 使用另一个解析器来接受HTML代码中的命名空间。在这里查看一个漂亮而详细的HTML解析器列表。这可能是最有效的方法。
  • 如果您想坚持使用DOMDocument,您基本上必须对代码进行预处理和后处理。

    • 在将代码发送到DOMDocument->loadHTML之前,使用正则表达式、循环或任何您想要的方式查找所有带命名空间的标记,并向包含命名空间的开放标记添加自定义属性。

      <fb:like send="true" width="450" show_faces="true"></fb:like>
      

      然后会得到

      <fb:like xmlNamespace="fb" send="true" width="450" show_faces="true"></fb:like>
      
    • 现在将编辑后的代码提供给DOMDocument->loadHTML。它将剥离命名空间,但保留属性,结果如下:

      <like xmlNamespace="fb" send="true" width="450" show_faces="true"></like>
      
    • 现在(再次使用正则表达式、循环或任何您想要的方式)查找所有具有xmlNamespace属性的标记,并用实际的命名空间替换该属性。不要忘记还要将命名空间添加到关闭标记中!

我不认为楼主还在寻找答案,我只是为了那些在研究中发现这篇帖子的人而发布这个回复。


这听起来像是一个非常直接的解决方案,所以我决定采用它。以下是我最终得出的代码,供那些讨厌正则表达式的人使用。// 存储任何命名空间元素,以便稍后重新添加 $postContent = preg_replace('/<(\w+):(\w+)/', '<\1 namespace="\2"' , $postContent);// 重构任何命名空间标签 $postContent = preg_replace('/<(\w+) namespace="(\w+)"/', '<\1:\2 ' , $postContent); - lupos

0

这是您要找的吗?

您可以尝试使用SimpleHTMLDOM。然后您可以运行类似于...

$html = new simple_html_dom();
$html->load_file('fileToParse.html');
$count=0;
foreach($html->find('fb:like') as $element){
    $count+=1
}
echo $count;

那应该可以工作。

我再看了一下,找到了这个。我从PHP.net上的DOMDocument中获取了这个。

$dom = new DOMDocument;
$dom->loadHTML('fileToParse.html'); // or $dom->loadXML('fileToParse.html'); 
$likes = $dom->getElementsByTagName('fb:like');
$count=0;
foreach ($likes as $like) {
    $count+=1;
}

在这之后我卡住了

$file=file_get_contents("other.html");
$search = '/<fb:like[^>]*>/';
$count  = preg_match_all($search , $file, $matches);
echo $count;
//Below is not needed
print_r($matches);

然而,那是正则表达式,速度相当慢。我尝试过:

$dom = new DOMDocument;
$xpath = new DOMXPath($dom);
$dom->load("other.html");
$xpath = new DOMXPath($dom);
$rootNamespace = $dom->lookupNamespaceUri($dom->namespaceURI); 
$xpath->registerNamespace('fb', $rootNamespace); 
$elementList = $xpath->query('//fb:like'); 

但是我得到了和你一样的错误。


我之前用过这个,但是为了速度起见,我想使用本地解决方案。不过,我可能不得不回到这个 :( - Obto
@Obto 我在我的小网站上使用它,所以速度方面没有问题。 - Jonathan
我已经更新了另一种解决方案,应该更快。 - Jonathan
很遗憾,这不起作用。在解析HTML时,fb命名空间前缀被剥离了。因此,在搜索时将找不到任何内容,您必须搜索“like”而不是“fb”。 - Obto
想过这样做,但页面根本无法解析。DOMDocument的loadHTML显然内置了大量的html信息。 - Obto
@Obto 我已经更新了代码,SimpleHTMLDOM和RegEx是我唯一能够使其正常工作的插件。抱歉。 - Jonathan

0

我还没有找到使用DOM的方法。我很惊讶正则表达式比DOMDocument慢,因为通常对我来说不是这种情况。strpos应该是最快的:

strpos($dom, '<fb:like');

这只能找到第一个出现的位置,但是你可以编写一个简单的递归函数来适当地更改偏移量。


-1

尝试了正则表达式的解决方案... 存在一个问题,即闭合标签不接受属性!

<ns namespace="node">text</ns>

最重要的是,正则表达式没有寻找闭合标签...

所以最后我做了一些丑陋的事情

$output = preg_replace('/<(\/?)(\w+):(\w+)/', '<\1\2thistaghasanamespace\3' , $output);

并且

$output = preg_replace('/<(\/?)(\w+)thistaghasanamespace(\w+)/', '<\1\2:\3' , $output);

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