https://github.com/Imangazaliev/DiDOM
Simple and fast HTML and XML parser
https://github.com/Imangazaliev/DiDOM
dom html html-parser parser xml xml-parser xpath
Last synced: 20 days ago
JSON representation
Simple and fast HTML and XML parser
- Host: GitHub
- URL: https://github.com/Imangazaliev/DiDOM
- Owner: Imangazaliev
- License: mit
- Created: 2015-08-15T16:18:34.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2024-03-15T06:37:48.000Z (about 1 year ago)
- Last Synced: 2025-04-10T11:01:41.242Z (25 days ago)
- Topics: dom, html, html-parser, parser, xml, xml-parser, xpath
- Language: PHP
- Homepage:
- Size: 457 KB
- Stars: 2,206
- Watchers: 86
- Forks: 202
- Open Issues: 23
-
Metadata Files:
- Readme: README-RU.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
- awesome-php - DiDOM - A super-fast HTML scrapper and parser. (Table of Contents / Scraping)
- awesome-projects - DiDOM - A super fast HTML scrapper and parser. (PHP / Scraping)
- awesome-php - DiDOM - A super-fast HTML scrapper and parser. (Table of Contents / Scraping)
- awesome-php-cn - DiDOM - 一个超级快速的HTML清理器和解析器. (目录 / 爬虫 Scraping)
- awesome-web-scraping - DiDOM - A blazing-fast and easy-to-use HTML parser. (Core Libraries / PHP)
- awesome-web-scraping - DiDOM - A blazing-fast and easy-to-use HTML parser. (Core Libraries / PHP)
- fucking-awesome-php - DiDOM - A super-fast HTML scrapper and parser. (Table of Contents / Scraping)
README
# DiDOM
[](https://travis-ci.org/Imangazaliev/DiDOM)
[](https://packagist.org/packages/imangazaliev/didom)
[](https://packagist.org/packages/imangazaliev/didom)
[](https://packagist.org/packages/imangazaliev/didom)DiDOM - простая и быстрая библиотека для парсинга HTML.
- [English version](README.md)
- [Документация для версии 1.x](https://github.com/Imangazaliev/DiDOM/blob/98d411741d598b0b74bb38e215d99c1cdb0d532d/README-RU.md). Чтобы обновится с версии 1.x, пожалуйста просмотрите [историю изменений](CHANGELOG.md).## Содержание
- [Установка](#Установка)
- [Быстрый старт](#Быстрый-старт)
- [Создание нового документа](#Создание-нового-документа)
- [Поиск элементов](#Поиск-элементов)
- [Проверка наличия элемента](#Проверка-наличия-элемента)
- [Подсчет количества элементов](#Подсчет-количества-элементов)
- [Поиск в элементе](#Поиск-в-элементе)
- [Поддерживамые селекторы](#Поддерживамые-селекторы)
- [Изменение содержимого](#Изменение-содержимого)
- [Вывод содержимого](#Вывод-содержимого)
- [Работа с элементами](#Работа-с-элементами)
- [Создание нового элемента](#Создание-нового-элемента)
- [Получение названия элемента](#Получение-названия-элемента)
- [Получение родительского элемента](#Получение-родительского-элемента)
- [Получение соседних элементов](#Получение-соседних-элементов)
- [Получение дочерних элементов](#Получение-соседних-элементов)
- [Получение документа](#Получение-документа)
- [Работа с атрибутами элемента](#Работа-с-атрибутами-элемента)
- [Сравнение элементов](#Сравнение-элементов)
- [Добавление дочерних элементов](#Добавление-дочерних-элементов)
- [Замена элемента](#Замена-элемента)
- [Удаление элемента](#Удаление-элемента)
- [Работа с кэшем](#Работа-с-кэшем)
- [Прочее](#Прочее)
- [Сравнение с другими парсерами](#Сравнение-с-другими-парсерами)## Установка
Для установки DiDOM выполните команду:
composer require imangazaliev/didom
## Быстрый старт
```php
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 несколькими способами:
##### Через конструктор
```php
// в первом параметре передается строка с 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);
```Сигнатура:
```php
__construct($string = null, $isFile = false, $encoding = 'UTF-8', $type = Document::TYPE_HTML)
````$isFile` - указывает, что загружается файл. По умолчанию - `false`.
`$encoding` - кодировка документа. По умолчанию - UTF-8.
`$type` - тип документа (HTML - `Document::TYPE_HTML`, XML - `Document::TYPE_XML`). По умолчанию - `Document::TYPE_HTML`.
##### Через отдельные методы
```php
$document = new Document();$document->loadHtml($html);
$document->loadHtmlFile('page.html');
$document->loadHtmlFile('http://www.example.com/');
```Для загрузки XML есть соответствующие методы `loadXml` и `loadXmlFile`.
При загрузке документа через эти методы, парсеру можно передать дополнительные [опции](http://php.net/manual/ru/libxml.constants.php):
```php
$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. Для этого в первом параметре нужно передать само выражение, а во втором - его тип (по умолчанию - `Query::TYPE_CSS`):
##### Через метод `find()`:
```php
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);
```Метод вернет массив с элементами (экземпляры класса `DiDom\Element`) или пустой массив, если не найден ни один элемент, соответствующий выражению.
При желании можно получить массив узлов без преобразования в Element или текст (`DOMElement`/`DOMText`/`DOMComment`/`DOMAttr`, в зависимости от выражения), для этого необходимо передать в качестве третьего параметра `false`.
##### Через метод `first()`:
Возвращает первый найденный элемент или `null`, если не найдено ни одного элемента.
Принимает те же параметры, что и метод `find()`.
##### Через магический метод `__invoke()`:
```php
$posts = $document('.post');
```Принимает те же параметры, что и метод `find()`.
**Внимание:** использование данного метода нежелательно, т.к. в будущем он может быть удален.
##### Через метод `xpath()`:
```php
$posts = $document->xpath("//*[contains(concat(' ', normalize-space(@class), ' '), ' post ')]");
```## Проверка наличия элемента
Проверить наличие элемента можно с помощью метода `has()`:
```php
if ($document->has('.post')) {
// код
}
```Если нужно проверить наличие элемента, а затем получить его, то можно сделать так:
```php
if ($document->has('.post')) {
$elements = $document->find('.post');// код
}
```но быстрее так:
```php
$elements = $document->find('.post');if (count($elements) > 0) {
// код
}
```т.к. в первом случае выполняется два запроса.
## Подсчет количества элементов
Метод `count()` позволяет подсчитать количество дочерних элементов, соотвествующих селектору:
```php
// выведет количество ссылок в документе
echo $document->count('a');
``````php
// выведет количество пунктов в списке
echo $document->first('ul')->count('> li');
```## Поиск в элементе
Методы `find()`, `first()`, `xpath()`, `has()`, `count()` доступны также и для элемента.
Пример:
```php
echo $document->find('nav')[0]->first('ul.menu')->xpath('//li')[0]->text();
```#### Метод `findInDocument()`
При изменении, замене или удалении элемента, найденного в другом элементе, документ не будет изменен. Данное поведение связано с тем, что в методе `find()` класса `Element` (а, соответственно, и в методах `first()` и `xpath`) создается новый документ, в котором и производится поиск.
Для поиска элементов в исходном документе необходимо использовать методы `findInDocument()` и `firstInDocument()`:
```php
// ничего не выйдет
$document->first('head')->first('title')->remove();// а вот так да
$document->first('head')->firstInDocument('title')->remove();
```**Внимание:** методы `findInDocument()` и `firstInDocument()` работают только для элементов, которые принадлежат какому-либо документу, либо созданых через `new Element(...)`. Если элемент не принадлежит к какому-либо документу, будет выброшено исключение `LogicException`;
## Поддерживамые селекторы
DiDom поддерживает поиск по:
- тэгу
- классу, идентификатору, имени и значению атрибута
- псевдоклассам:
- first-, last-, nth-child
- empty и not-empty
- contains
- has```php
// все ссылки
$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
```php
$element->setInnerHtml('Foo');
```### Изменение XML
```php
$element->setInnerXml(' Foo BarHello world!
]]>');
```### Изменение значения (как простой текст)
```php
$element->setValue('Foo');
// будет закодирован в HTML-сущность как при вызове htmlentities()
$element->setValue('Foo');
```## Вывод содержимого
### Получение HTML
##### Через метод `html()`:
```php
// HTML-код документа
echo $document->html();// HTML-код элемента
echo $document->first('.post')->html();
```##### Приведение к строке:
```php
// HTML-код документа
$html = (string) $document;// HTML-код элемента
$html = (string) $document->first('.post');
```**Внимание:** использование данного способа нежелательно, т.к. в будущем он может быть удален.
##### Форматирование HTML при выводе
```php
echo $document->format()->html();
```Метод `format()` отсутствует у элемента, поэтому, если нужно получить отформатированный HTML-код элемента, необходимо сначала преобразовать его в документ:
```php
$html = $element->toDocument()->format()->html();
```#### Внутренний HTML
```php
$innerHtml = $element->innerHtml();
```Метод `innerHtml()` отсутствует у документа, поэтому, если нужно получить внутренний HTML-код документа, необходимо сначала преобразовать его в элемент:
```php
$innerHtml = $document->toElement()->innerHtml();
```### Получение XML
```php
// XML-код документа
echo $document->xml();// XML-код элемента
echo $document->first('book')->xml();
```### Получение содержимого
Возвращает текстовое содержимое узла и его потомков:
```php
echo $element->text();
```## Создание нового элемента
### Создание экземпляра класса
```php
use DiDom\Element;$element = new Element('span', 'Hello');
// выведет "Hello"
echo $element->html();
```Первым параметром передается название элемента, вторым - его значение (необязательно), третьим - атрибуты элемента (необязательно).
Пример создания элемента с атрибутами:
```php
$attributes = ['name' => 'description', 'placeholder' => 'Enter description of item'];$element = new Element('textarea', 'Text', $attributes);
```Элемент можно создать и из экземпляра класса `DOMElement`:
```php
use DiDom\Element;
use DOMElement;$domElement = new DOMElement('span', 'Hello');
$element = new Element($domElement);
```#### Изменение элемента, созданного из `DOMElement`
Экземпляры класса `DOMElement`, созданные через конструктор (`new DOMElement(...)`), являются неизменяемыми, поэтому и элементы (экземпляры класса `DiDom\Element`), созданные из таких объектов, так же являются неизменяемыми.
Пример:
```php
$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()`
```php
$document = new Document($html);$element = $document->createElement('span', 'Hello');
```### С помощью CSS-селектора
Первый параметр - селектор, второй - значение, третий - массив с атрибутами.
Атрибуты элемента могут быть указаны как в селекторе, так и переданы отдельно в третьем параметре.
Если название атрибута в массиве совпадает с названием атрибута из селектора, будет использовано значение, указанное в селекторе.
```php
$document = new Document($html);$element = $document->createElementBySelector('div.block', 'Foo', [
'id' => '#content',
'class' => '.container',
]);
```Можно так же использовать статический метод `createBySelector` класса `Element`:
```php
$element = Element::createBySelector('div.block', 'Foo', [
'id' => '#content',
'class' => '.container',
]);
```## Получение названия элемента
```php
$element->tagName();
```## Получение родительского элемента
```php
$element->parent();
```Так же можно получить родительский элемент, соответствующий селектору:
```php
$element->closest('.foo');
```Вернет родительский элемент, у которого есть класс `foo`. Если подходящий элемент не найден, метод вернет `null`.
## Получение соседних элементов
Первый аргумент - CSS-селектор, второй - тип узла (`DOMElement`, `DOMText` или `DOMComment`).
Если оба аргумента опущены, будет осуществлен поиск узлов любого типа.
Если селектор указан, а тип узла нет, будет использован тип `DOMElement`.
**Внимание:** Селектор можно использовать только с типом `DOMElement`.
```php
// предыдущий элемент
$item->previousSibling();// предыдущий элемент, соответствующий селектору
$item->previousSibling('span');// предыдущий элемент типа DOMElement
$item->previousSibling(null, 'DOMElement');// предыдущий элемент типа DOMComment
$item->previousSibling(null, 'DOMComment');
``````php
// все предыдущие элементы
$item->previousSiblings();// все предыдущие элементы, соответствующие селектору
$item->previousSiblings('span');// все предыдущие элементы типа DOMElement
$item->previousSiblings(null, 'DOMElement');// все предыдущие элементы типа DOMComment
$item->previousSiblings(null, 'DOMComment');
``````php
// следующий элемент
$item->nextSibling();// следующий элемент, соответствующий селектору
$item->nextSibling('span');// следующий элемент типа DOMElement
$item->nextSibling(null, 'DOMElement');// следующий элемент типа DOMComment
$item->nextSibling(null, 'DOMComment');
``````php
// все последующие элементы
$item->nextSiblings();// все последующие элементы, соответствующие селектору
$item->nextSiblings('span');// все последующие элементы типа DOMElement
$item->nextSiblings(null, 'DOMElement');// все последующие элементы типа DOMComment
$item->nextSiblings(null, 'DOMComment');
```## Получение дочерних элементов
```php
$html = 'FooBar';$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());
```## Получение документа
```php
$document = new Document($html);$element = $document->first('input[name=email]');
$document2 = $element->ownerDocument();
// bool(true)
var_dump($document->is($document2));
```## Работа с атрибутами элемента
#### Создание/изменение атрибута
##### Через метод `setAttribute`:
```php
$element->setAttribute('name', 'username');
```##### Через метод `attr`:
```php
$element->attr('name', 'username');
```##### Через магический метод `__set`:
```php
$element->name = 'username';
```#### Получение значения атрибута
##### Через метод `getAttribute`:
```php
$username = $element->getAttribute('value');
```##### Через метод `attr`:
```php
$username = $element->attr('value');
```##### Через магический метод `__get`:
```php
$username = $element->name;
```Если атрибут не найден, вернет `null`.
#### Проверка наличия атрибута
##### Через метод `hasAttribute`:
```php
if ($element->hasAttribute('name')) {
// код
}
```##### Через магический метод `__isset`:
```php
if (isset($element->name)) {
// код
}
```#### Удаление атрибута:
##### Через метод `removeAttribute`:
```php
$element->removeAttribute('name');
```##### Через магический метод `__unset`:
```php
unset($element->name);
```#### Получение всех атрибутов:
```php
var_dump($element->attributes());
```#### Получение определенных атрибутов:
```php
var_dump($element->attributes(['name', 'type']));
```#### Удаление всех атрибутов:
```php
$element->removeAllAttributes();
```#### Удаление всех атрибутов, за исключением указанных:
```php
$element->removeAllAttributes(['name', 'type']);
```## Сравнение элементов
```php
$element = new Element('span', 'hello');
$element2 = new Element('span', 'hello');// bool(true)
var_dump($element->is($element));// bool(false)
var_dump($element->is($element2));
```## Добавление дочерних элементов
```php
$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);
```## Замена элемента
```php
$title = new Element('title', 'foo');$document->first('title')->replace($title);
```**Внимание:** заменить можно только те элементы, которые были найдены непосредственно в документе:
```php
// ничего не выйдет
$document->first('head')->first('title')->replace($title);// а вот так да
$document->first('head title')->replace($title);
```Подробнее об этом в разделе [Поиск в элементе](#Поиск-в-элементе).
## Удаление элемента
```php
$document->first('title')->remove();
```**Внимание:** удалить можно только те элементы, которые были найдены непосредственно в документе:
```php
// ничего не выйдет
$document->first('head')->first('title')->remove();// а вот так да
$document->first('head title')->remove();
```Подробнее об этом в разделе [Поиск в элементе](#Поиск-в-элементе).
## Работа с кэшем
Кэш - массив XPath-выражений, полученных из CSS.
#### Получение кэша
```php
use DiDom\Query;...
$xpath = Query::compile('h2');
$compiled = Query::getCompiled();// array('h2' => '//h2')
var_dump($compiled);
```#### Установка кэша
```php
Query::setCompiled(['h2' => '//h2']);
```## Прочее
#### `preserveWhiteSpace`
По умолчанию сохранение пробелов между тегами отключено.
Включать опцию `preserveWhiteSpace` следует до загрузки документа:
```php
$document = new Document();$document->preserveWhiteSpace();
$document->loadXml($xml);
```#### `matches`
Возвращает `true`, если элемент соответсвует селектору:
```php
// вернет true, если элемент это div с идентификатором content
$element->matches('div#content');// строгое соответствие
// вернет true, если элемент это div с идентификатором content и ничего более
// если у элемента будут какие-либо другие атрибуты, метод вернет false
$element->matches('div#content', true);
```#### `isElementNode`
Проверяет, является ли элемент узлом типа DOMElement:
```php
$element->isElementNode();
```#### `isTextNode`
Проверяет, является ли элемент текстовым узлом (DOMText):
```php
$element->isTextNode();
```#### `isCommentNode`
Проверяет, является ли элемент комментарием (DOMComment):
```php
$element->isCommentNode();
```## Сравнение с другими парсерами
[Сравнение с другими парсерами](https://github.com/Imangazaliev/DiDOM/wiki/Сравнение-с-другими-парсерами-(1.6.3))