Поскольку большинство (все?) 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);
}