Поскольку большинство (всех?) Библиотек PHP, которые выполняют очистку HTML, например HTML Purifier, сильно зависят от регулярных выражений, я подумал, что попытка написать средство очистки 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);
}