Как удалить в PHP внешние теги с узла

#php #domdocument

#php #domdocument

Вопрос:

У меня есть следующий html-код:

 $pageHTML = '<html>
<head></head>
<body>
<div class="some class">
<header>Header</header>
<section>Section</section>
<footer>Footer</footer>
</div>
</body>
</html>';
  

и мне нужно удалить внешние теги <div> , сохранив весь его внутренний HTML внутри <body>

Если я попытаюсь

 $dom = new DOMDocument;
libxml_use_internal_errors(true);
$dom->loadHTML($pageHTML);
libxml_use_internal_errors(false);

$bodyDivs = [];
foreach($dom->getElementsByTagName('body')[0]->childNodes as $bodyChild) {
    if($bodyChild->nodeName == 'div') {
        $bodyDivs[] = $bodyChild;
    }
}

if(count($bodyDivs) == 1) {
    foreach($bodyDivs[0]->childNodes as $divChild) {
        $dom->getElementsByTagName('body')[0]->appendChild($divChild);
    }
    $dom->getElementsByTagName('body')[0]->removeChild($bodyDivs[0]);
}
  

div удаляется, но без добавления его дочерних элементов в <body> перед удалением

Если я попробую обратный цикл, подобный

 $k = count($bodyDivs[0]->childNodes);
for($n = $k-1; $n >= 0; $n--) {
    $dom->getElementsByTagName('body')[0]->appendChild($bodyDivs[0]->childNodes[$n]);
}
$dom->getElementsByTagName('body')[0]->removeChild($bodyDivs[0]);
  

дочерние теги добавляются в тело, но в обратном порядке

Итак, я получаю

 <body>
<footer>Footer</footer>
<section>Section</section>
<header>Header</header>
</body>
  

но мне нужно

 <body>
<header>Header</header>
<section>Section</section>
<footer>Footer</footer>
</body>
  

Как решить проблему?

Комментарии:

1. То, что вы написали, очень запутанно, возможно, это из-за перевода, но не могли бы вы опубликовать в качестве обновления то, как вы хотите $pageHTML выглядеть после выполнения этого.

2. Обновлено на @ArtisticPhoenix

Ответ №1:

Ваш исходный код очень близок, просто отсутствует один ключевой момент.

Исходный код

 foreach($bodyDivs[0]->childNodes as $divChild) {
    $dom->getElementsByTagName('body')[0]->appendChild($divChild);
}
  

Попытка получить foreach список узлов, одновременно удаляя узлы из этого же списка (в вашем случае, перемещая их в <body> ), ведет себя не так, как вы предполагали.

Упрощенный, полный пример для демонстрационных целей:

 <?php
$doc = new DOMDocument;
$doc->loadXML('<example><a/><b/><c/><d/><e/></example>');
$parent = $doc->documentElement;
foreach ($parent->childNodes as $child) {
    $parent->removeChild($child);
}
echo $doc->saveXML();
  

Это выводит следующее:

 <?xml version="1.0"?>
<example><b/><c/><d/><e/></example>
  

Совершенно разумно, не так ли?! Не бойтесь, мы можем сделать лучше.

Что делать?

Распространенный подход, который ведет себя так, как задумано, заключается в циклическом просмотре списка до тех пор, пока он не станет пустым.

 <?php
$doc = new DOMDocument;
$doc->loadXML('<example><a/><b/><c/><d/><e/></example>');
$parent = $doc->documentElement;
while ($parent->childNodes->length > 0) {
    $child = $parent->childNodes->item(0);
    $parent->removeChild($child);
}
echo $doc->saveXML();
  

Применяется к вашему коду

Все вышесказанное означает, что ваш исходный foreach :

 foreach($bodyDivs[0]->childNodes as $divChild) {
    $dom->getElementsByTagName('body')[0]->appendChild($divChild);
}
  

Может быть заменен циклом while.

 while ($bodyDivs[0]->childNodes->length > 0) {
    $divChild = $bodyDivs[0]->childNodes->item(0);
    $dom->getElementsByTagName('body')->item(0)->appendChild($divChild);
}
  

В сторону: я использовал ->item(0) обозначения выше, поскольку это более общепринято.

Ответ №2:

Хорошо, я нашел свое собственное решение, но, возможно, кто-нибудь опубликует более элегантный:

 if(count($bodyDivs) == 1) {

    $count = count($bodyDivs[0]->childNodes);

    $arr = [];
    for($n = $count-1; $n >= 0; $n--) {
        $arr[] = $bodyDivs[0]->childNodes[$n];
    }

    for($n = $count-1; $n >= 0; $n--) {
        $dom->getElementsByTagName('body')[0]->appendChild($arr[$n]);
    }

    $dom->getElementsByTagName('body')[0]->removeChild($bodyDivs[0]);
}

echo str_replace("nr", "", $dom->saveHTML((new DOMXPath($dom))->query('/')->item(0)));