DiDOM
DiDOM
DiDOM - простая и быстрая библиотека для парсинга HTML.
Содержание
- Установка
- Быстрый старт
- Создание нового документа
- Поиск элементов
- Проверка наличия элемента
- Подсчет количества элементов
- Поиск в элементе
- Поддерживамые селекторы
- Изменение содержимого
- Вывод содержимого
- Работа с элементами
- Работа с кэшем
- Прочее
- Сравнение с другими парсерами
Установка
Для установки DiDOM выполните команду:
composer require imangazaliev/didom
Быстрый старт
use DiDom\Document;
$document = new Document('http://www.news.com/', true);
$posts = $document->find('.post');
foreach($posts as $post) { echo $post->text(), "\n";}
Создание нового документа
DiDom позволяет загрузить HTML несколькими способами:
Через конструктор
// в первом параметре передается строка с HTML$document = new Document($html);
// путь к файлу$document = new Document('page.html', true);
// или URL$document = new Document('http://www.example.com/', true);
// также можно создать документ из DOMDocument$domDocument = new DOMDocument();$document = new Document($domDocument);
Сигнатура:
__construct($string = null, $isFile = false, $encoding = 'UTF-8', $type = Document::TYPE_HTML)
- указывает, что загружается файл. По умолчанию -
.
- кодировка документа. По умолчанию - UTF-8.
- тип документа (HTML -
, XML -
). По умолчанию -
.
Через отдельные методы
$document = new Document();
$document->loadHtml($html);
$document->loadHtmlFile('page.html');
$document->loadHtmlFile('http://www.example.com/');
Для загрузки XML есть соответствующие методы
и
.
При загрузке документа через эти методы, парсеру можно передать дополнительные опции:
$document->loadHtml($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);$document->loadHtmlFile($url, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$document->loadXml($xml, LIBXML_PARSEHUGE);$document->loadXmlFile($url, LIBXML_PARSEHUGE);
Поиск элементов
В качестве выражения для поиска можно передать CSS-селектор или XPath. Для этого в первом параметре нужно передать само выражение, а во втором - его тип (по умолчанию -
):
Через метод find()
:
use DiDom\Document;use DiDom\Query;
...
// CSS-селектор$posts = $document->find('.post');
// эквивалентно$posts = $document->find('.post', Query::TYPE_CSS);
// XPath-выражение$posts = $document->find("//div[contains(@class, 'post')]", Query::TYPE_XPATH);
Метод вернет массив с элементами (экземпляры класса
) или пустой массив, если не найден ни один элемент, соответствующий выражению.
При желании можно получить массив узлов без преобразования в Element или текст (
/
/
/
, в зависимости от выражения), для этого необходимо передать в качестве третьего параметра
.
Через метод first()
:
Возвращает первый найденный элемент или
, если не найдено ни одного элемента.
Принимает те же параметры, что и метод
.
Через магический метод __invoke()
:
$posts = $document('.post');
Принимает те же параметры, что и метод
.
Внимание: использование данного метода нежелательно, т.к. в будущем он может быть удален.
Через метод xpath()
:
$posts = $document->xpath("//*[contains(concat(' ', normalize-space(@class), ' '), ' post ')]");
Проверка наличия элемента
Проверить наличие элемента можно с помощью метода
:
if ($document->has('.post')) { // код}
Если нужно проверить наличие элемента, а затем получить его, то можно сделать так:
if ($document->has('.post')) { $elements = $document->find('.post');
// код}
но быстрее так:
$elements = $document->find('.post');
if (count($elements) > 0) { // код}
т.к. в первом случае выполняется два запроса.
Подсчет количества элементов
Метод
позволяет подсчитать количество дочерних элементов, соотвествующих селектору:
// выведет количество ссылок в документеecho $document->count('a');
// выведет количество пунктов в спискеecho $document->first('ul')->count('> li');
Поиск в элементе
Методы
,
,
,
,
доступны также и для элемента.
Пример:
echo $document->find('nav')[0]->first('ul.menu')->xpath('//li')[0]->text();
Метод findInDocument()
При изменении, замене или удалении элемента, найденного в другом элементе, документ не будет изменен. Данное поведение связано с тем, что в методе
класса
(а, соответственно, и в методах
и
) создается новый документ, в котором и производится поиск.
Для поиска элементов в исходном документе необходимо использовать методы
и
:
// ничего не выйдет$document->first('head')->first('title')->remove();
// а вот так да$document->first('head')->firstInDocument('title')->remove();
Внимание: методы
и
работают только для элементов, которые принадлежат какому-либо документу, либо созданых через
. Если элемент не принадлежит к какому-либо документу, будет выброшено исключение
;
Поддерживамые селекторы
DiDom поддерживает поиск по:
- тэгу
- классу, идентификатору, имени и значению атрибута
- псевдоклассам:
- first-, last-, nth-child
- empty и not-empty
- contains
- has
// все ссылки$document->find('a');
// любой элемент с id = "foo" и классом "bar"$document->find('#foo.bar');
// любой элемент, у которого есть атрибут "name"$document->find('[name]');
// эквивалентно$document->find('*[name]');
// поле ввода с именем "foo"$document->find('input[name=foo]');$document->find('input[name=\'foo\']');$document->find('input[name="foo"]');
// поле ввода с именем "foo" и значением "bar"$document->find('input[name="foo"][value="bar"]');
// поле ввода, название которого НЕ равно "foo"$document->find('input[name!="foo"]');
// любой элемент, у которого есть атрибут,// начинающийся с "data-" и равный "foo"$document->find('*[^data-=foo]');
// все ссылки, у которых адрес начинается с https$document->find('a[href^=https]');
// все изображения с расширением png$document->find('img[src$=png]');
// все ссылки, содержащие в своем адресе строку "example.com"$document->find('a[href*=example.com]');
// все ссылки, содержащие в атрибуте data-foo значение bar отделенное пробелом$document->find('a[data-foo~=bar]');
// текст всех ссылок с классом "foo" (массив строк)$document->find('a.foo::text');
// эквивалентно$document->find('a.foo::text()');
// адрес и текст подсказки всех полей с классом "bar"$document->find('a.bar::attr(href|title)');
// все ссылки, которые являются прямыми потомками текущего элемента$element->find('> a');
Изменение содержимого
Изменение HTML
$element->setInnerHtml('<a href="#">Foo</a>');
Изменение значения
$element->setValue('Foo');
Вывод содержимого
Получение HTML
Через метод html()
:
// HTML-код документаecho $document->html();
// HTML-код элементаecho $document->first('.post')->html();
Приведение к строке:
// HTML-код документа$html = (string) $document;
// HTML-код элемента$html = (string) $document->first('.post');
Внимание: использование данного способа нежелательно, т.к. в будущем он может быть удален.
Форматирование HTML при выводе
echo $document->format()->html();
Метод
отсутствует у элемента, поэтому, если нужно получить отформатированный HTML-код элемента, необходимо сначала преобразовать его в документ:
$html = $element->toDocument()->format()->html();
Внутренний HTML
$innerHtml = $element->innerHtml();
Метод
отсутствует у документа, поэтому, если нужно получить внутренний HTML-код документа, необходимо сначала преобразовать его в элемент:
$innerHtml = $document->toElement()->innerHtml();
Получение XML
// XML-код документаecho $document->xml();
// XML-код элементаecho $document->first('book')->xml();
Получение содержимого
Возвращает текстовое содержимое узла и его потомков:
echo $element->text();
Создание нового элемента
Создание экземпляра класса
use DiDom\Element;
$element = new Element('span', 'Hello');
// выведет "<span>Hello</span>"echo $element->html();
Первым параметром передается название элемента, вторым - его значение (необязательно), третьим - атрибуты элемента (необязательно).
Пример создания элемента с атрибутами:
$attributes = ['name' => 'description', 'placeholder' => 'Enter description of item'];
$element = new Element('textarea', 'Text', $attributes);
Элемент можно создать и из экземпляра класса
:
use DiDom\Element;use DOMElement;
$domElement = new DOMElement('span', 'Hello');$element = new Element($domElement);
Изменение элемента, созданного из DOMElement
Экземпляры класса
, созданные через конструктор (
), являются неизменяемыми, поэтому и элементы (экземпляры класса
), созданные из таких объектов, так же являются неизменяемыми.
Пример:
$element = new Element('span', 'Hello');
// добавит атрибут "id" со значением "greeting"$element->attr('id', 'greeting');
$domElement = new DOMElement('span', 'Hello');$element = new Element($domElement);
// будет выброшено исключение// DOMException with message 'No Modification Allowed Error'$element->attr('id', 'greeting');
С помощью метода Document::createElement()
$document = new Document($html);
$element = $document->createElement('span', 'Hello');
С помощью CSS-селектора
Первый параметр - селектор, второй - значение, третий - массив с атрибутами.
Атрибуты элемента могут быть указаны как в селекторе, так и переданы отдельно в третьем параметре.
Если название атрибута в массиве совпадает с названием атрибута из селектора, будет использовано значение, указанное в селекторе.
$document = new Document($html);
$element = $document->createElementBySelector('div.block', 'Foo', [ 'id' => '#content', 'class' => '.container',]);
Можно так же использовать статический метод
класса
:
$element = Element::createBySelector('div.block', 'Foo', [ 'id' => '#content', 'class' => '.container', ]);
Получение названия элемента
$element->tag;
Получение родительского элемента
$element->parent();
Так же можно получить родительский элемент, соответствующий селектору:
$element->closest('.foo');
Вернет родительский элемент, у которого есть класс
. Если подходящий элемент не найден, метод вернет
.
Получение соседних элементов
Первый аргумент - CSS-селектор, второй - тип узла (
,
или
).
Если оба аргумента опущены, будет осуществлен поиск узлов любого типа.
Если селектор указан, а тип узла нет, будет использован тип
.
Внимание: Селектор можно использовать только с типом
.
// предыдущий элемент$item->previousSibling();
// предыдущий элемент, соответствующий селектору$item->previousSibling('span');
// предыдущий элемент типа DOMElement$item->previousSibling(null, 'DOMElement');
// предыдущий элемент типа DOMComment$item->previousSibling(null, 'DOMComment');
// все предыдущие элементы$item->previousSiblings();
// все предыдущие элементы, соответствующие селектору$item->previousSiblings('span');
// все предыдущие элементы типа DOMElement$item->previousSiblings(null, 'DOMElement');
// все предыдущие элементы типа DOMComment$item->previousSiblings(null, 'DOMComment');
// следующий элемент$item->nextSibling();
// следующий элемент, соответствующий селектору$item->nextSibling('span');
// следующий элемент типа DOMElement$item->nextSibling(null, 'DOMElement');
// следующий элемент типа DOMComment$item->nextSibling(null, 'DOMComment');
// все последующие элементы$item->nextSiblings();
// все последующие элементы, соответствующие селектору$item->nextSiblings('span');
// все последующие элементы типа DOMElement$item->nextSiblings(null, 'DOMElement');
// все последующие элементы типа DOMComment$item->nextSiblings(null, 'DOMComment');
Получение дочерних элементов
$html = '<div>Foo<span>Bar</span><!--Baz--></div>';
$document = new Document($html);
$div = $document->first('div');
// элемент (DOMElement)// string(3) "Bar"var_dump($div->child(1)->text());
// текстовый узел (DOMText)// string(3) "Foo"var_dump($div->firstChild()->text());
// комментарий (DOMComment)// string(3) "Baz"var_dump($div->lastChild()->text());
// array(3) { ... }var_dump($div->children());
Получение документа
$document = new Document($html);
$element = $document->first('input[name=email]');
$document2 = $element->getDocument();
// bool(true)var_dump($document->is($document2));
Работа с атрибутами элемента
Создание/изменение атрибута
Через метод setAttribute
:
$element->setAttribute('name', 'username');
Через метод attr
:
$element->attr('name', 'username');
Через магический метод __set
:
$element->name = 'username';
Получение значения атрибута
Через метод getAttribute
:
$username = $element->getAttribute('value');
Через метод attr
:
$username = $element->attr('value');
Через магический метод __get
:
$username = $element->name;
Если атрибут не найден, вернет
.
Проверка наличия атрибута
Через метод hasAttribute
:
if ($element->hasAttribute('name')) { // код}
Через магический метод __isset
:
if (isset($element->name)) { // код}
Удаление атрибута:
Через метод removeAttribute
:
$element->removeAttribute('name');
Через магический метод __unset
:
unset($element->name);
Получение всех атрибутов:
var_dump($element->attributes());
Получение определенных атрибутов:
var_dump($element->attributes(['name', 'type']));
Удаление всех атрибутов:
$element->removeAllAttributes();
Удаление всех атрибутов, за исключением указанных:
$element->removeAllAttributes(['name', 'type']);
Сравнение элементов
$element = new Element('span', 'hello');$element2 = new Element('span', 'hello');
// bool(true)var_dump($element->is($element));
// bool(false)var_dump($element->is($element2));
Добавление дочерних элементов
$list = new Element('ul');
$item = new Element('li', 'Item 1');
$list->appendChild($item);
$items = [ new Element('li', 'Item 2'), new Element('li', 'Item 3'),];
$list->appendChild($items);
Замена элемента
$title = new Element('title', 'foo');
$document->first('title')->replace($title);
Внимание: заменить можно только те элементы, которые были найдены непосредственно в документе:
// ничего не выйдет$document->first('head')->first('title')->replace($title);
// а вот так да$document->first('head title')->replace($title);
Подробнее об этом в разделе Поиск в элементе.
Удаление элемента
$document->first('title')->remove();
Внимание: удалить можно только те элементы, которые были найдены непосредственно в документе:
// ничего не выйдет$document->first('head')->first('title')->remove();
// а вот так да$document->first('head title')->remove();
Подробнее об этом в разделе Поиск в элементе.
Работа с кэшем
Кэш - массив XPath-выражений, полученных из CSS.
Получение кэша
use DiDom\Query;
...
$xpath = Query::compile('h2');$compiled = Query::getCompiled();
// array('h2' => '//h2')var_dump($compiled);
Установка кэша
Query::setCompiled(['h2' => '//h2']);
Прочее
preserveWhiteSpace
По умолчанию сохранение пробелов между тегами отключено.
Включать опцию
следует до загрузки документа:
$document = new Document();
$document->preserveWhiteSpace();
$document->loadXml($xml);
matches
Возвращает
, если элемент соответсвует селектору:
// вернет true, если элемент это div с идентификатором content$element->matches('div#content');
// строгое соответствие// вернет true, если элемент это div с идентификатором content и ничего более// если у элемента будут какие-либо другие атрибуты, метод вернет false$element->matches('div#content', true);
isElementNode
Проверяет, является ли элемент узлом типа DOMElement:
$element->isElementNode();
isTextNode
Проверяет, является ли элемент текстовым узлом (DOMText):
$element->isTextNode();
isCommentNode
Проверяет, является ли элемент комментарием (DOMComment):
$element->isCommentNode();