DiDOM - simple and fast HTML parser.
To install DiDOM run the command:
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 allows to load HTML in several ways:
// the first parameter is a string with HTML
$document = new Document($html);
// file path
$document = new Document('page.html', true);
// or URL
$document = new Document('http://www.example.com/', true);
The second parameter specifies if you need to load file. Default is false
.
Signature:
__construct($string = null, $isFile = false, $encoding = 'UTF-8', $type = Document::TYPE_HTML)
$string
- an HTML or XML string or a file path.
$isFile
- indicates that the first parameter is a path to a file.
$encoding
- the document encoding.
$type
- the document type (HTML - Document::TYPE_HTML
, XML - Document::TYPE_XML
).
$document = new Document();
$document->loadHtml($html);
$document->loadHtmlFile('page.html');
$document->loadHtmlFile('http://www.example.com/');
There are two methods available for loading XML: loadXml
and loadXmlFile
.
These methods accept additional options:
$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);
DiDOM accepts CSS selector or XPath as an expression for search. You need to path expression as the first parameter, and specify its type in the second one (default type is Query::TYPE_CSS
):
find()
:use DiDom\Document;
use DiDom\Query;
...
// CSS selector
$posts = $document->find('.post');
// XPath
$posts = $document->find("//div[contains(@class, 'post')]", Query::TYPE_XPATH);
If the elements that match a given expression are found, then method returns an array of instances of DiDom\Element
, otherwise - an empty array. You could also get an array of DOMElement
objects. To get this, pass false
as the third parameter.
__invoke()
:$posts = $document('.post');
Warning: using this method is undesirable because it may be removed in the future.
xpath()
:$posts = $document->xpath("//*[contains(concat(' ', normalize-space(@class), ' '), ' post ')]");
You can do search inside an element:
echo $document->find('nav')[0]->first('ul.menu')->xpath('//li')[0]->text();
To verify if element exist use has()
method:
if ($document->has('.post')) {
// code
}
If you need to check if element exist and then get it:
if ($document->has('.post')) {
$elements = $document->find('.post');
// code
}
but it would be faster like this:
if (count($elements = $document->find('.post')) > 0) {
// code
}
because in the first case it makes two queries.
Methods find()
, first()
, xpath()
, has()
, count()
are available in Element too.
Example:
echo $document->find('nav')[0]->first('ul.menu')->xpath('//li')[0]->text();
findInDocument()
If you change, replace, or remove an element that was found in another element, the document will not be changed. This happens because method find()
of Element
class (a, respectively, the first ()
and xpath
methods) creates a new document to search.
To search for elements in the source document, you must use the methods findInDocument()
and firstInDocument()
:
// nothing will happen
$document->first('head')->first('title')->remove();
// but this will do
$document->first('head')->firstInDocument('title')->remove();
Warning: methods findInDocument()
and firstInDocument()
work only for elements, which belong to a document, and for elements created via new Element(...)
. If an element does not belong to a document, LogicException
will be thrown;
DiDom supports search by:
pseudo-classes:
has
// all links
$document->find('a');
// any element with id = "foo" and "bar" class
$document->find('#foo.bar');
// any element with attribute "name"
$document->find('[name]');
// the same as
$document->find('*[name]');
// input field with the name "foo"
$document->find('input[name=foo]');
$document->find('input[name=\'bar\']');
$document->find('input[name="baz"]');
// any element that has an attribute starting with "data-" and the value "foo"
$document->find('*[^data-=foo]');
// all links starting with https
$document->find('a[href^=https]');
// all images with the extension png
$document->find('img[src$=png]');
// all links containing the string "example.com"
$document->find('a[href*=example.com]');
// text of the links with "foo" class
$document->find('a.foo::text');
// address and title of all the fields with "bar" class
$document->find('a.bar::attr(href|title)');
$element->setInnerHtml('<a href="#">Foo</a>');
$element->setInnerXml(' Foo <span>Bar</span><!-- Baz --><![CDATA[
<root>Hello world!</root>
]]>');
$element->setValue('Foo');
// will be encoded like using htmlentities()
$element->setValue('<a href="#">Foo</a>');
html()
:$posts = $document->find('.post');
echo $posts[0]->html();
$html = (string) $posts[0];
$html = $document->format()->html();
An element does not have format()
method, so if you need to output formatted HTML of the element, then first you have to convert it to a document:
$html = $element->toDocument()->format()->html();
$innerHtml = $element->innerHtml();
Document does not have the method innerHtml()
, therefore, if you need to get inner HTML of a document, convert it into an element first:
$innerHtml = $document->toElement()->innerHtml();
echo $document->xml();
echo $document->first('book')->xml();
$posts = $document->find('.post');
echo $posts[0]->text();
use DiDom\Element;
$element = new Element('span', 'Hello');
// Outputs "<span>Hello</span>"
echo $element->html();
First parameter is a name of an attribute, the second one is its value (optional), the third one is element attributes (optional).
An example of creating an element with attributes:
$attributes = ['name' => 'description', 'placeholder' => 'Enter description of item'];
$element = new Element('textarea', 'Text', $attributes);
An element can be created from an instance of the class DOMElement
:
use DiDom\Element;
use DOMElement;
$domElement = new DOMElement('span', 'Hello');
$element = new Element($domElement);
createElement
$document = new Document($html);
$element = $document->createElement('span', 'Hello');
$element->tagName();
$document = new Document($html);
$input = $document->find('input[name=email]')[0];
var_dump($input->parent());
$document = new Document($html);
$item = $document->find('ul.menu > li')[1];
var_dump($item->previousSibling());
var_dump($item->nextSibling());
$html = '<div>Foo<span>Bar</span><!--Baz--></div>';
$document = new Document($html);
$div = $document->first('div');
// element node (DOMElement)
// string(3) "Bar"
var_dump($div->child(1)->text());
// text node (DOMText)
// string(3) "Foo"
var_dump($div->firstChild()->text());
// comment node (DOMComment)
// string(3) "Baz"
var_dump($div->lastChild()->text());
// array(3) { ... }
var_dump($div->children());
$document = new Document($html);
$element = $document->find('input[name=email]')[0];
$document2 = $element->ownerDocument();
// 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;
Returns null
if attribute is not found.
hasAttribute
:if ($element->hasAttribute('name')) {
// code
}
__isset
:if (isset($element->name)) {
// code
}
removeAttribute
:$element->removeAttribute('name');
__unset
:unset($element->name);
$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);
$list = new Element('ul');
$item = new Element('li', 'Item 1');
$items = [
new Element('li', 'Item 2'),
new Element('li', 'Item 3'),
];
$list->appendChild($item);
$list->appendChild($items);
$element = new Element('span', 'hello');
$document->find('.post')[0]->replace($element);
Waning: you can replace only those elements that were found directly in the document:
// nothing will happen
$document->first('head')->first('title')->replace($title);
// but this will do
$document->first('head title')->replace($title);
More about this in section Search for elements.
$document->find('.post')[0]->remove();
Warning: you can remove only those elements that were found directly in the document:
// nothing will happen
$document->first('head')->first('title')->remove();
// but this will do
$document->first('head title')->remove();
More about this in section Search for elements.
Cache is an array of XPath expressions, that were converted from CSS.
use DiDom\Query;
...
$xpath = Query::compile('h2');
$compiled = Query::getCompiled();
// array('h2' => '//h2')
var_dump($compiled);
Query::setCompiled(['h2' => '//h2']);
preserveWhiteSpace
By default, whitespace preserving is disabled.
You can enable the preserveWhiteSpace
option before loading the document:
$document = new Document();
$document->preserveWhiteSpace();
$document->loadXml($xml);
count
The count ()
method counts children that match the selector:
// prints the number of links in the document
echo $document->count('a');
// prints the number of items in the list
echo $document->first('ul')->count('li');
matches
Returns true
if the node matches the selector:
$element->matches('div#content');
// strict match
// returns true if the element is a div with id equals content and nothing else
// if the element has any other attributes the method returns false
$element->matches('div#content', true);
isElementNode
Checks whether an element is an element (DOMElement):
$element->isElementNode();
isTextNode
Checks whether an element is a text node (DOMText):
$element->isTextNode();
isCommentNode
Checks whether the element is a comment (DOMComment):
$element->isCommentNode();