php - 如何在 PHP 中删除重复的、嵌套的 DOM 元素?

标签 php html dom

假设您有一个带有嵌套标签的 DOM 树,我想通过删除重复项来清理 DOM 对象。但是,这只适用于标签只有一个子标签的情况同一类型。例如,

修复 <div><div>1</div></div>而不是 <div><div>1</div><div>2</div></div> .

我正在尝试弄清楚如何使用 PHP's DOM extension 来做到这一点.下面是起始代码,我正在寻求帮助来确定所需的逻辑。

<?php

libxml_use_internal_errors(TRUE);

$html = '<div><div><div><p>Some text here</p></div></div></div>';

$dom = new DOMDocument;
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadHTML($html);

function dom_remove_duplicate_nodes($node)
{
    var_dump($node);

    if($node->hasChildNodes())
    {
        for($i = 0; $i < $node->childNodes->length; $i++)
        {
            $child = $node->childNodes->item($i);

            dom_remove_duplicate_nodes($child);
        }
    }
    else
    {
        // Process here?
    }
}

dom_remove_duplicate_nodes($dom);

我收集了一些帮助函数,这些函数可以使像 JavaScript 一样更容易地处理 DOM 节点。

function DOM_delete_node($node)
{
    DOM_delete_children($node);
    return $node->parentNode->removeChild($node);
}

function DOM_delete_children($node)
{
    while (isset($node->firstChild))
    {
        DOM_delete_children($node->firstChild);
        $node->removeChild($node->firstChild);
    }
}

function DOM_dump_child_nodes($node)
{
    $output = '';
    $owner_document = $node->ownerDocument;

    foreach ($node->childNodes as $el)
    {
        $output .= $owner_document->saveHTML($el);
    }
    return $output;
}

function DOM_dump_node($node)
{
    if($node->ownerDocument)
    {
        return $node->ownerDocument->saveHTML($node);
    }
}

最佳答案

您可以使用 DOMDocumentDOMXPath 轻松完成此操作. XPath 在您的情况下尤其有用,因为您可以轻松划分逻辑以选择要删除的元素以及删除元素的方式。

首先,规范化输入。我不完全清楚你对空空格的意思,我认为它可能是空文本节点(可能已被删除,因为 preserveWhiteSpaceFALSE 但我不确定)或者它们是否规范化空白为空。我选择了第一个(如果有必要的话),以防它是另一个变体,我留下了评论使用什么来代替:

$xp = new DOMXPath($dom);

//remove empty textnodes - if necessary at all
// (in case remove WS: [normalize-space()=""])
foreach($xp->query('//text()[""]') as $i => $tn)
{
    $tn->parentNode->removeChild($tn);
}

在此 textnode 规范化之后,您应该不会遇到您在此处评论中提到的问题。

下一部分是找到所有与其父元素同名并且是唯一子元素的元素。这又可以用xpath来表示。如果找到这样的元素,则将其所有子元素移动到父元素,然后该元素也将被删除:

// all child elements with same name as parent element and being
// the only child element.
$r = $xp->query('body//*/child::*[name(.)=name(..) and count(../child::*)=1]');
foreach($r as $i => $dupe)
{
    while($dupe->childNodes->length)
    {
        $child = $dupe->firstChild;
        $dupe->removeChild($child);
        $dupe->parentNode->appendChild($child);
    }   
    $dupe->parentNode->removeChild($dupe);
}

Full demo .

如您在演示中所见,这独立于文本节点和注释。如果你不想要那个,例如在实际文本中,计算子项的表达式需要覆盖所有节点类型。但我不知道这是否是您的确切需求。如果是,则计算所有节点类型的子节点数:

body//*/child::*[name(.)=name(..) and count(../child::node())=1]

如果您没有预先规范化空文本节点(删除空文本节点),那么这就太严格了。选择你需要的工具集,我认为规范化加上这个严格的规则可能是最好的选择。

关于php - 如何在 PHP 中删除重复的、嵌套的 DOM 元素?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7972199/

相关文章:

PHP 数组 - 保存到 MySQL 时出现奇怪的数据

php - 在 MySql 中将 2 行计数加在一起

html - CSS位置 Angular 绝对左下角

html - CSS 溢出 : visible not working

Javascript 在特定类出现后在 div 中包装多个 p 标签

javascript - 使用 JS 解析 HTML 文本 - 额外节点?

php - MySQL使用PDO插入没有插入数据

php - 有哪些好的 PHP MySQL 调试工具?

javascript - 我不能在我的 HTML 中使用 Vue js 的 JS 脚本

javascript - JS点击事件监听器没有按预期触发功能