Обходить дерево DOM

Поскольку большинство (все?) PHP-библиотек, выполняющих очистку HTML, таких как очиститель HTML, сильно зависят от регулярного выражения , Я подумал, что попытка написать средство дезинфекции HTML, использующее DOMDocument и связанные классы, будет стоящим экспериментом. Хотя я нахожусь на очень ранней стадии этого проекта, пока он подает некоторые надежды.

Моя идея вращается вокруг класса, который использует DOMDocument для обхода всех узлов в предоставленной разметке, сравнения их с белым списком и удаления всего, чего нет в белом списке. (первая реализация очень проста, удаляются только узлы в зависимости от их типа, но я надеюсь стать более сложным и проанализировать атрибуты узла, адресуют ли ссылки элементы в другом домене и т.д. в будущем).

У меня вопрос: как пройти по дереву DOM? Насколько я понимаю, объекты DOM * имеют атрибут childNodes, так что нужно ли мне выполнять рекурсию по всему дереву? Кроме того, ранние эксперименты с DOMNodeLists показали, что вам нужно очень внимательно относиться к порядку удаления вещей, иначе вы можете оставить элементы позади или вызвать исключения.

Если у кого-то есть опыт работы с деревом DOM в PHP, я буду признателен за любые отзывы, которые вы можете получить по этой теме.

РЕДАКТИРОВАТЬ: Я создал следующий метод для своего класса очистки 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);
}
9
задан GordonM 15 June 2011 в 12:12
поделиться