我正在使用XPath操作一个简短的HTML代码段;当我使用$doc->saveHTML()输出更改的代码段时,DOCTYPE
被添加,并且HTML / BODY
标签包装输出。我想删除这些,但仅使用 DOMDocument 函数将所有孩子都保留在里面。 例如:
$doc = new DOMDocument();
$doc->loadHTML('<p><strong>Title...</strong></p>
<a href="http://www....."><img src="http://" alt=""></a>
<p>...to be one of those crowning achievements...</p>');
// manipulation goes here
echo htmlentities( $doc->saveHTML() );
这会产生:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" ...>
<html><body>
<p><strong>Title...</strong></p>
<a href="http://www....."><img src="http://" alt=""></a>
<p>...to be one of those crowning achievements...</p>
</body></html>
我尝试了一些简单的技巧,例如:
# removes doctype
$doc->removeChild($doc->firstChild);
# <body> replaces <html>
$doc->replaceChild($doc->firstChild->firstChild, $doc->firstChild);
到目前为止,这只删除了 DOCTYPE 并用 BODY 替换了 HTML。 但是,此时剩下的是可变数量的元素>主体。
如何删除 <body>
标签但保留其所有子标签,因为它们的结构各不相同,使用 PHP 的 DOM 操作以整洁干净的方式?
更新
这是一个不扩展 DOMDocument 的版本,尽管我认为扩展是正确的方法,因为您正在尝试实现未内置于 DOM API 的功能。
注意:我将"干净"和"没有解决方法"解释为保留对 DOM API 的所有操作。一旦您点击字符串操作,这就是解决方法领域。
我正在做的,就像在最初的答案中一样,是利用DOMDocumentFragment来操作位于根级别的多个节点。没有正在进行的字符串操作,对我来说,这不是一种解决方法。
$doc = new DOMDocument();
$doc->loadHTML('<p><strong>Title...</strong></p><a href="http://www....."><img src="http://" alt=""></a><p>...to be one of those crowning achievements...</p>');
// Remove doctype node
$doc->doctype->parentNode->removeChild($doc->doctype);
// Remove html element, preserving child nodes
$html = $doc->getElementsByTagName("html")->item(0);
$fragment = $doc->createDocumentFragment();
while ($html->childNodes->length > 0) {
$fragment->appendChild($html->childNodes->item(0));
}
$html->parentNode->replaceChild($fragment, $html);
// Remove body element, preserving child nodes
$body = $doc->getElementsByTagName("body")->item(0);
$fragment = $doc->createDocumentFragment();
while ($body->childNodes->length > 0) {
$fragment->appendChild($body->childNodes->item(0));
}
$body->parentNode->replaceChild($fragment, $body);
// Output results
echo htmlentities($doc->saveHTML());
原始答案
这个解决方案相当冗长,但它是通过扩展 DOM 来实现的,以便使你的最终代码尽可能短。
sliceOutNode
是魔术发生的地方。如果您有任何问题,请告诉我:
<?php
class DOMDocumentExtended extends DOMDocument
{
public function __construct( $version = "1.0", $encoding = "UTF-8" )
{
parent::__construct( $version, $encoding );
$this->registerNodeClass( "DOMElement", "DOMElementExtended" );
}
// This method will need to be removed once PHP supports LIBXML_NOXMLDECL
public function saveXML( DOMNode $node = NULL, $options = 0 )
{
$xml = parent::saveXML( $node, $options );
if( $options & LIBXML_NOXMLDECL )
{
$xml = $this->stripXMLDeclaration( $xml );
}
return $xml;
}
public function stripXMLDeclaration( $xml )
{
return preg_replace( "|<'?xml(.+?)'?>['n'r]?|i", "", $xml );
}
}
class DOMElementExtended extends DOMElement
{
public function sliceOutNode()
{
$nodeList = new DOMNodeListExtended( $this->childNodes );
$this->replaceNodeWithNode( $nodeList->toFragment( $this->ownerDocument ) );
}
public function replaceNodeWithNode( DOMNode $node )
{
return $this->parentNode->replaceChild( $node, $this );
}
}
class DOMNodeListExtended extends ArrayObject
{
public function __construct( $mixedNodeList )
{
parent::__construct( array() );
$this->setNodeList( $mixedNodeList );
}
private function setNodeList( $mixedNodeList )
{
if( $mixedNodeList instanceof DOMNodeList )
{
$this->exchangeArray( array() );
foreach( $mixedNodeList as $node )
{
$this->append( $node );
}
}
elseif( is_array( $mixedNodeList ) )
{
$this->exchangeArray( $mixedNodeList );
}
else
{
throw new DOMException( "DOMNodeListExtended only supports a DOMNodeList or array as its constructor parameter." );
}
}
public function toFragment( DOMDocument $contextDocument )
{
$fragment = $contextDocument->createDocumentFragment();
foreach( $this as $node )
{
$fragment->appendChild( $contextDocument->importNode( $node, true ) );
}
return $fragment;
}
// Built-in methods of the original DOMNodeList
public function item( $index )
{
return $this->offsetGet( $index );
}
public function __get( $name )
{
switch( $name )
{
case "length":
return $this->count();
break;
}
return false;
}
}
// Load HTML/XML using our fancy DOMDocumentExtended class
$doc = new DOMDocumentExtended();
$doc->loadHTML('<p><strong>Title...</strong></p><a href="http://www....."><img src="http://" alt=""></a><p>...to be one of those crowning achievements...</p>');
// Remove doctype node
$doc->doctype->parentNode->removeChild( $doc->doctype );
// Slice out html node
$html = $doc->getElementsByTagName("html")->item(0);
$html->sliceOutNode();
// Slice out body node
$body = $doc->getElementsByTagName("body")->item(0);
$body->sliceOutNode();
// Pick your poison: XML or HTML output
echo htmlentities( $doc->saveXML( NULL, LIBXML_NOXMLDECL ) );
echo htmlentities( $doc->saveHTML() );
saveHTML
可以输出文档的子集,这意味着我们可以要求它通过遍历 body 逐个输出每个子节点。
$doc = new DOMDocument();
$doc->loadHTML('<p><strong>Title...</strong></p>
<a href="http://google.com"><img src="http://google.com/img.jpeg" alt=""></a>
<p>...to be one of those crowning achievements...</p>');
// manipulation goes here
// Let's traverse the body and output every child node
$bodyNode = $doc->getElementsByTagName('body')->item(0);
foreach ($bodyNode->childNodes as $childNode) {
echo $doc->saveHTML($childNode);
}
这可能不是一个最优雅的解决方案,但它有效。或者,我们可以将所有子节点包装在某个容器元素(例如div
)中并仅输出该容器(但容器标签将包含在输出中)。
这是我是如何做到的:
-- 快速助手功能,为您提供特定 DOM 元素的 HTML 内容
function nodeContent($n, $outer=false) { $d = new DOMDocument('1.0'); $b = $d->importNode($n->cloneNode(true),true); $d->附录儿童($b);$h = $d->saveHTML(); 删除外部标记 if (!$outer) $h = substr($h,strpos($h,'>')+1,-(strlen($n->nodeName)+4)); 返回$h;}
-- 在文档中查找正文节点并获取其内容
$query = $xpath->query("//body")->item(0);如果($query){ 回显节点内容($query);}
更新 1:
一些额外的信息:从 PHP/5.3.6 开始,DOMDocument->saveHTML() 接受可选的 DOMNode 参数,类似于 DOMDocument->saveXML()。你可以做
$xpath = new DOMXPath($doc);$query = $xpath->query("//body")->item(0);echo $doc->saveHTML($query);
对于其他人,帮助程序功能会有所帮助
tl;dr
要求:PHP 5.4.0
和Libxml 2.6.0
$doc->loadHTML("<p>test</p>", LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
解释
http://php.net/manual/en/domdocument.loadhtml.php"从 PHP 5.4.0 和 Libxml 2.6.0 开始,您还可以使用 options 参数来指定其他 Libxml 参数。
LIBXML_HTML_NOIMPLIED
设置HTML_PARSE_NOIMPLIED标志,关闭自动添加隐含的 html/正文...元素。
LIBXML_HTML_NODEFDTD
设置HTML_PARSE_NODEFDTD标志,防止在找不到默认文档类型时添加默认文档类型。
您有两种方法可以实现此目的:
$content = substr($content, strpos($content, '<html><body>') + 12); // Remove Everything Before & Including The Opening HTML & Body Tags.
$content = substr($content, 0, -14); // Remove Everything After & Including The Closing HTML & Body Tags.
或者更好的是这样:
$dom->normalizeDocument();
$content = $dom->saveHTML();