#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)));