jjzjj

php - 遍历 DOM 树

coder 2024-01-01 原文

由于大多数(所有?)执行 HTML 清理的 PHP 库(例如 HTML Purifier)都严重依赖于正则表达式,因此我认为尝试编写一个使用 DOMDocument 和相关类的 HTML 清理器将是一个值得尝试的实验。虽然我还处于非常早期的阶段,但该项目到目前为止显示出一些希望。

我的想法围绕一个类展开,该类使用 DOMDocument 遍历提供的标记中的所有节点,将它们与白名单进行比较,并删除不在白名单上的任何内容。 (第一个实现是非常基本的,只是根据节点的类型删除节点,但我希望将来能够变得更复杂并分析节点的属性,链接是否指向不同域中的项目等)。

我的问题是如何遍历 DOM 树?据我了解,DOM* 对象有一个 childNodes 属性,所以我需要递归整个树吗?此外,早期使用 DOMNodeLists 进行的实验表明,您需要非常注意删除内容的顺序,否则您可能会留下项目或触发异常。

如果有人有在 PHP 中操作 DOM 树的经验,我将不胜感激您对这个主题的任何反馈。

编辑:我为我的 HTML 清理类构建了以下方法。它递归地遍历 DOM 树并检查找到的元素是否在白名单中。如果不是,则将其删除。

我遇到的问题是,如果删除一个节点,DOMNodeList 中所有后续节点的索引都会更改。简单地从下往上工作可以避免这个问题。目前它仍然是一种非常基本的方法,但我认为它显示出希望。它的运行速度肯定比 HTMLPurifier 快得多,尽管不可否认 Purifier 做了更多的事情。

/**
 * Recursivly remove elements from the DOM that aren't whitelisted
 * @param DOMNode $elem
 * @return array List of elements removed from the DOM
 * @throws Exception If removal of a node failed than an exception is thrown
 */
private function cleanNodes (DOMNode $elem)
{
    $removed    = array ();
    if (in_array ($elem -> nodeName, $this -> whiteList))
    {
        if ($elem -> hasChildNodes ())
        {
            /*
             * Iterate over the element's children. The reason we go backwards is because
             * going forwards will cause indexes to change when elements get removed
             */
            $children   = $elem -> childNodes;
            $index      = $children -> length;
            while (--$index >= 0)
            {
                $removed = array_merge ($removed, $this -> cleanNodes ($children -> item ($index)));
            }
        }
    }
    else
    {
        // The element is not on the whitelist, so remove it
        if ($elem -> parentNode -> removeChild ($elem))
        {
            $removed [] = $elem;
        }
        else
        {
            throw new Exception ('Failed to remove node from DOM');
        }
    }
    return ($removed);
}

最佳答案

首先,您可以看一下这个自定义的 RecursiveDomIterator:

代码:

class RecursiveDOMIterator implements RecursiveIterator
{
    /**
     * Current Position in DOMNodeList
     * @var Integer
     */
    protected $_position;

    /**
     * The DOMNodeList with all children to iterate over
     * @var DOMNodeList
     */
    protected $_nodeList;

    /**
     * @param DOMNode $domNode
     * @return void
     */
    public function __construct(DOMNode $domNode)
    {
        $this->_position = 0;
        $this->_nodeList = $domNode->childNodes;
    }

    /**
     * Returns the current DOMNode
     * @return DOMNode
     */
    public function current()
    {
        return $this->_nodeList->item($this->_position);
    }

    /**
     * Returns an iterator for the current iterator entry
     * @return RecursiveDOMIterator
     */
    public function getChildren()
    {
        return new self($this->current());
    }

    /**
     * Returns if an iterator can be created for the current entry.
     * @return Boolean
     */
    public function hasChildren()
    {
        return $this->current()->hasChildNodes();
    }

    /**
     * Returns the current position
     * @return Integer
     */
    public function key()
    {
        return $this->_position;
    }

    /**
     * Moves the current position to the next element.
     * @return void
     */
    public function next()
    {
        $this->_position++;
    }

    /**
     * Rewind the Iterator to the first element
     * @return void
     */
    public function rewind()
    {
        $this->_position = 0;
    }

    /**
     * Checks if current position is valid
     * @return Boolean
     */
    public function valid()
    {
        return $this->_position < $this->_nodeList->length;
    }
}

您可以将它与 RecursiveIteratorIterator 结合使用。使用示例在页面上。

但一般来说,使用 XPath 搜索黑名单节点比遍历 DOM 树更容易。还要记住,DOM 已经非常擅长通过自动转义 nodeValues 中的 xml 实体来防止 XSS。

您必须注意的另一件事是,对 DOMDocument 的任何操作都会立即影响您可能从 XPath 查询中获得的任何 DOMNodeList,并且这可能会导致在操作它们时跳过节点。参见 DOMNode replacement with PHP's DOM classes举个例子。

关于php - 遍历 DOM 树,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6356115/

有关php - 遍历 DOM 树的更多相关文章

  1. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  2. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  3. ruby - Chef Ruby 遍历 .erb 模板文件中的属性 - 2

    所以这可能有点令人困惑,但请耐心等待。简而言之,我想遍历具有特定键值的所有属性,然后如果值不为空,则将它们插入到模板中。这是我的代码:属性:#===DefaultfileConfigurations#default['elasticsearch']['default']['ES_USER']=''default['elasticsearch']['default']['ES_GROUP']=''default['elasticsearch']['default']['ES_HEAP_SIZE']=''default['elasticsearch']['default']['MAX_OP

  4. ruby - 如何遍历 Ruby 中所有正则表达式匹配的字符串? - 2

    我们有一个字符串:“”这个正则表达式://i如何从当前字符串中获取所有匹配项? 最佳答案 "".scan(//)参见scan在ruby​​-docs上 关于ruby-如何遍历Ruby中所有正则表达式匹配的字符串?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/6857852/

  5. ruby - 循环遍历数组的元素 - 2

    我想从0到2循环@a:0,1,2,0,1,2。defset_aif@a==2@a=0else@a=@a+1endend也许有更好的方法? 最佳答案 (0..2).cycle(3){|x|putsx}#=>0,1,2,0,1,2,0,1,2item=[0,1,2].cycle.eachitem.next#=>0item.next#=>1item.next#=>2item.next#=>0... 关于ruby-循环遍历数组的元素,我们在StackOverflow上找到一个类似的问题:

  6. ruby-on-rails - 这个 C 和 PHP 程序员如何学习 Ruby 和 Rails? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我来自C、php和bash背景,很容易学习,因为它们都有相同的C结构,我可以将其与我已经知道的联系起来。然后2年前我学了Python并且学得很好,Python对我来说比Ruby更容易学。然后从去年开始,我一直在尝试学习Ruby,然后是Rails,我承认,直到现在我还是学不会,讽刺的是那些打着简单易学的烙印,但是对于我这样一个老练的程序员来说,我只是无法将它

  7. ruby-on-rails - 如何在 Rails View 中遍历数组? - 2

    我在MySql中进行了查询,但在Rails和mysql2gem中工作。信息如下:http://sqlfiddle.com/#!2/9adb8/6查询工作正常,没有问题,并显示以下结果:UNITV1A1N1V2A2N2V3A3N3V4A4N4V5A5N5LIFE200120000000000ROB010012000000000-为rails2.3.8安装了mysql2gemgeminstallmysql2-v0.2.6-创建Controller:classPolicyController这是日志:SQL(0.9ms)selectdistinct@sql:=concat('SELECTpb

  8. ruby - Ruby 中的目录遍历 - 2

    我一直在尝试使用简单的递归方法在Ruby中为一个更大的程序的一部分实现目录遍历。但是我发现Dir.foreach不包括其中的目录。我怎样才能列出它们?代码:defwalk(start)Dir.foreach(start)do|x|ifx=="."orx==".."nextelsifFile.directory?(x)walk(x)elseputsxendendend 最佳答案 问题是每次递归,你传递给File.directory?的路径isno只是实体(文件或目录)名称;所有上下文都丢失了。所以说你进入one/two/three/检

  9. ruby - Watir 无法找到我在 Chrome 的 DOM 检查器中看到的元素 - 2

    这是一个研究案例:......我正在尝试使用WatirRuby的API引用名为“bar”的嵌入元素。该元素由Chrome的DOM检查器显示,但我无法使用Watir的任何查找方法找到它:browser.embeds()#onlyisfoundbrowser.html.include?'bar'#=>false为什么会这样?为什么Watir不显示完整的HTML?如果我有不同框架中的元素或由Javascript初始化函数动态插入的元素,是否可以使用Watir访问它们?谢谢 最佳答案 如果元素在框架中,你必须使用这样的东西:browser.

  10. ruby - 遍历目录和子目录中的每个 .jpg 或 .jpeg 文件 - 2

    我想遍历目录中的每个jpg/jpeg文件以及每个子目录和该子目录的每个子目录等等。我希望能够浏览文件夹中的每个图像文件。有没有一种简单的方法可以做到这一点,或者递归方法是否效果最好? 最佳答案 Dir.glob("your_directory/**/*.{jpg,jpeg}") 关于ruby-遍历目录和子目录中的每个.jpg或.jpeg文件,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questi

随机推荐