DOMDocument — класс для работы с XML-документами.
Данный инструмент удобно использовать, когда вам необходимо парсить xml/html-документы.
Простой пример использования класса DOMDocument:
$dom = new DOMDocument();
$dom->loadHTML( $html );
$dom->saveHTML();
Рассмотрим основные ошибки и сложности с которыми можно столкнуться:
- Кодировка
- Невалидный документ
- Выбор елементов
- Удаление елементов
Кодировка
Для начала документ нужно загрузить с корректной кодировка. В случае, если мы работает не со всей страницей, а например с каким-то блоком, то элементов head и body нет и соответственно тега для указания кодировка тоже нет. DOMDocument доставит теги head и body самостоятельно, но кодировку нам нужно будет указать самостоятельно:
$dom->loadHTML( '<meta charset="utf-8">' . $html );
// OR
$dom->loadHTML( '<?xml encoding="utf-8" ?>' . $html );
Так же документ необходимо правильно сохранить:
$dom->saveHTML( $dom->documentElement );
Иногда нам нужно на выходе получить конкретный елемент, а не весь документ, то мы так же можем это сделать:
$dom->saveHTML( $dom->getElementById( 'element' ) );
Если нужно получить всю контентную часть тогда сохранение будет выглядеть примерно так:
$html = '';
$body_items = $dom->getElementsByTagName( 'body' )->item( 0 )->childNodes;
foreach ( $body_items as $node ) {
$html .= $dom->saveHTML( $node );
}
Иногда приходится работать с невалидными документами. При загрузке DOMDocument исправит разметку. Можно убрать warnings с помощью следующей конструкции:
libxml_use_internal_errors( true );
$dom->loadHTML( $html );
libxml_clear_errors();
Выборки
Простые выборки можно сделать с помощью методов DOMDocument:
public getElementById ( string $elementId ) : DOMElement
public getElementsByTagName ( string $name ) : DOMNodeList
public getElementsByTagNameNS ( string $namespaceURI , string $localName ) : DOMNodeList
Любые выборки можно сделать с помощью объекта класса DOMXPath, который реализовывает спецификацию XPath:
Селектор | Действие |
---|---|
/h1 | Выбрать все теги h1 в корне документа |
body/h1 | Выбрать все теги h1 которые внутри body |
body//h1 | Выбрать все теги h1 которые внутри body не зависимо от вложенности |
//h1 | Выбрать все теги h1 |
//h1[@title] | Выбрать все теги h1 с атрибутом title |
//h1[@title=»Name title»] | Выбрать все теги h1 с атрибутом title=»Name title» |
//h1[span] | Выбрать все теги h1 с тегом span внутри |
//h1[span=»Some text»] | Выбрать все теги h1 с тегом <span>Some text</span> внутри |
//h1[span>40] | Выбрать все теги h1 с тегом span у которого nodeValue > 40 |
* | Выбрать все элементы |
//@* | Выбрать все атрибуты |
//h1/* | Выбрать все дочерние элементы тега h1 |
//title[@*] | Выбрать все элементы с атрибутов title |
//h1 | //h2 | Выбрать все элементы h1 и h2 |
XPath — не так прост, чем привычные всем CSS-селекторы, но XPath мощнее и может включать в себя функции, операторы сравнении, поиск элемента с дочерним элементом и другое.
Примеры функций XPath:
- contains()
- text()
- starts-with()
- sum()
- count()
//a[starts-with( @href, ‘#’ )] | Выбрать все теги a, у которых ссылка начинается с символа «#» |
Проблема удаления
Если мы хотим удалить узел, то весь документ перестраивается. Следующий код отработает правильно только при первом удалении.
foreach( $elements as $element ) {
$element->parentNode->removeChild($href);
}
Решением является перебор елементов начиная с конца массива:
for ( $i = $elements->length; --$i >= 0; ) {
$element = $elements->item($i);
$element->parentNode->removeChild($href);
}