Перемещение дерева DOM

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

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

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

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

EDIT: Я создал следующий метод для моего класса очистки 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); } 

Для начала вы можете взглянуть на этот пользовательский рекурсивныйDomIterator:

Код:

 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 Tree. Также имейте в виду, что DOM уже неплохо предотвращает XSS, автоматически удаляя xml-объекты в nodeValues.

Другая вещь, о которой вы должны знать, заключается в том, что любая манипуляция с DOMDocument немедленно повлияет на любой DOMNodeList, который может возникнуть из запросов XPath, и может привести к пропущенным узлам при их манипулировании. Для примера см. Замену DOMNode с помощью классов DOM PHP .