Удалить родительский элемент, сохранить все внутренние дочерние элементы в DOMDocument с помощью saveHTML

Я манипулирую небольшим фрагментом 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?

Related of "Удалить родительский элемент, сохранить все внутренние дочерние элементы в DOMDocument с помощью saveHTML"

ОБНОВИТЬ

Вот версия, которая не расширяет 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();