如何从节点中删除 PHP 外部标签

How to remove in PHP outer tags from a node

我有以下 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>

如何解决问题?

好的,我找到了自己的解决方案,但也许有人会 post 更优雅:

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("\n\r", "", $dom->saveHTML((new \DOMXPath($dom))->query('/')->item(0)));

你的原代码很接近,只是少了一个关键点。

原码

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) 符号,因为它更传统。