Я манипулирую небольшим фрагментом HTML с XPath; когда я возвращаю измененный фрагмент обратно с помощью $ 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 и заменяет HTML с помощью BODY. Однако то, что остается, – это тело> переменное количество элементов в этой точке.
Как удалить <body>
но сохранить все его дочерние элементы, учитывая, что они будут структурированы по-разному, аккуратно-чистым способом с использованием DOM-манипуляций PHP?
Вот версия, которая не расширяет 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
может выводить подмножество документа, то есть мы можем попросить его вывести каждый дочерний узел один за другим, пройдя тело.
$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
) и вывести только этот контейнер (но тег контейнера будет включен в выход).
Вот как я это сделал:
– Быстрая вспомогательная функция, которая дает HTML-содержимое для конкретного элемента DOM
function nodeContent ($ n, $ outer = false) { $ d = новый DOMDocument ('1.0'); $ b = $ d-> importNode ($ n-> cloneNode (true), true); $ D-> AppendChild ($ б); $ h = $ d-> saveHTML (); // удалять теги outter if (! $ external) $ h = substr ($ h, strpos ($ h, '>') + 1, - (strlen ($ n-> nodeName) +4)); return $ h; }
– Найти узел тела в своем документе и получить его содержимое
$ query = $ xpath-> query ("// body") -> item (0); если ($ запроса) { echo nodeContent ($ 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);
для других вспомогательная функция поможет
У вас есть 2 способа сделать это:
$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();