This is a new paragraph.
');
// add a class and some attribute to all paragraphs
$page->filter('p')->addClass('newclass')->setAttribute('data-foo', 'bar');
// get HTML content of an element
echo $page->filter('#content')->saveHTML();
// output the whole HTML page
echo $page->save();
// or simply:
echo $page;
// output formatted HTML code
echo $page->indent()->save();
// output compressed (minified) HTML code
echo $page->minify()->save();
```
See also the generated [API doc for HtmlPage](doc/HtmlPage.md)
Limitations
-----------
- HtmlPageDom builds on top of PHP's DOM functions and uses the loadHTML() and saveHTML() methods of the DOMDocument class.
That's why it's output is always HTML, not XHTML.
- The HTML parser used by PHP is built for HTML4. It throws errors
on HTML5 specific elements which are ignored by HtmlPageDom, so HtmlPageDom is usable for HTML5 with some limitations.
- HtmlPageDom has not been tested with character encodings other than UTF-8.
History
-------
When I discovered how easy it was to modify HTML documents using jQuery I looked for a PHP library providing similar
possibilities for PHP.
Googling around I found [SimpleHtmlDom](http://simplehtmldom.sourceforge.net)
and later [Ganon](http://code.google.com/p/ganon) but both turned out to be very slow. Nevertheless I used both
libraries in my projects.
When Symfony2 appeared with it's DomCrawler and CssSelector components I thought:
the functions for traversing the DOM tree and selecting elements by CSS selectors are already there, only the
manipulation functions are missing. Let's implement them! So the HtmlPageDom project was born.
It turned out that it was a good choice to build on PHP's DOM functions: Compared to SimpleHtmlDom and Ganon, HmtlPageDom
is lightning fast. In one of my projects, I have a PHP script that takes a huge HTML page containing several hundreds
of article elements and extracts them into individual HTML files (that are later on demand loaded by AJAX back into the
original HTML page). Using SimpleHtmlDom it took the script 3 minutes (right, minutes!) to run (and I needed to raise
PHP's memory limit to over 500MB). Using Ganon as HTML parsing and manipulation engine it took even longer,
about 5 minutes. After switching to HtmlPageDom the same script doing the same processing tasks is running only about
one second (all on the same server). HtmlPageDom is really fast.
© 2012-2023 Christoph Singer. Licensed under the MIT License.
================================================
FILE: Resources/jquerytest.html
================================================
This page contains javascript code to figure out in which cases jQuery returns references to existing objects
and when it makes copies.
Title');
$this->assertEquals('
Title
', $c->saveHTML());
$c = new HtmlPageCrawler();
$c->addContent('asdf
asdfaf
');
$this->assertEquals(2, count($c));
$this->assertEquals('
asdf
asdfaf
', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::before
*/
public function testBefore()
{
$c = new HtmlPageCrawler('
Title
');
$c->filter('h1')->before('
Text before h1
');
$this->assertEquals('
', $c->saveHTML());
$c = new HtmlPageCrawler('
Title
');
$c->filter('h1')->before(new HtmlPageCrawler('
Text before h1
and more text before
'));
$this->assertEquals('
Text before h1
and more text before
Title
', $c->saveHTML());
$c = new HtmlPageCrawler('
Self Before
');
$c->filter('h1')->before($c->filter('h1'));
$this->assertEquals('
Self Before
', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::insertBefore
*/
public function testInsertBefore()
{
$c = new HtmlPageCrawler('
');
$c->filter('p')->insertBefore($c->filter('h1'));
$this->assertEquals('
', $c->saveHTML());
$c = new HtmlPageCrawler('
Self Insert Before Title
Text after h1
');
$c->filter('h1')->insertBefore($c->filter('h1'));
$this->assertEquals('
Self Insert Before Title
Text after h1
', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::after
*/
public function testAfter()
{
$c = new HtmlPageCrawler('
Title
');
$c->filter('h1')->after('
Text after h1
');
$this->assertEquals('
', $c->saveHTML());
$c = new HtmlPageCrawler('
Title
Title2
');
$c->filter('h1')->after(new HtmlPageCrawler('
Text after h1
and more text after
'));
$this->assertEquals('
Title
Text after h1
and more text after
Title2
Text after h1
and more text after
', $c->saveHTML());
$c = new HtmlPageCrawler('
Self After
');
$c->filter('h1')->after($c->filter('h1'));
$this->assertEquals('
Self After
', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::insertAfter
*/
public function testInsertAfter()
{
$c = new HtmlPageCrawler('
');
$c->filter('p')->insertAfter($c->filter('h1'));
$this->assertEquals('
', $c->saveHTML());
$c = new HtmlPageCrawler('
Text before h1
Self Insert After Title
');
$c->filter('h1')->insertAfter($c->filter('h1'));
$this->assertEquals('
Text before h1
Self Insert After Title
', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::prepend
*/
public function testPrepend()
{
$c = new HtmlPageCrawler('
Title
');
$c->filter('#content')->prepend('
Text before h1
');
$this->assertEquals('
', $c->saveHTML());
$c = new HtmlPageCrawler('
');
$c->filter('#content')->prepend(new HtmlPageCrawler('
Text before h1
and more text before
'));
$this->assertEquals('
Text before h1
and more text before
', $c->saveHTML());
$c = new HtmlPageCrawler('
Prepend Self
');
$c->filter('#content')->prepend($c->filter('span'));
$this->assertEquals('
Prepend Self
', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::prependTo
*/
public function testPrependTo()
{
$c = new HtmlPageCrawler('
');
$c->filter('p')->prependTo('Text');
$this->assertEquals('
', $c->saveHTML());
$c = new HtmlPageCrawler('
Title
');
$c->filter('#content')->prependTo(new HtmlPageCrawler('
paragraph
'));
$this->assertEquals('
Title
', $c->saveHTML());
$c = new HtmlPageCrawler('
Title
Big');
$c->filter('em')->prependTo($c->filter('h1'));
$this->assertEquals('
BigTitle
', $c->saveHTML());
$c = new HtmlPageCrawler('
Self Title
');
$c->filter('h1')->prependTo($c->filter('h1'));
$this->assertEquals('
Self Title
', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::wrap
*/
public function testWrap()
{
$c = new HtmlPageCrawler('
Title
');
$c->filter('h1')->wrap('
');
$this->assertEquals('
', $c->saveHTML());
$c = new HtmlPageCrawler('
Title
');
$c->filter('h1')->wrap('
');
$this->assertEquals('
', $c->saveHTML());
$c = new HtmlPageCrawler('
Title
');
$c->filter('h1')->wrap('
asdf
jkl
'); // wrap has more than 1 root element
$this->assertEquals('
', $c->saveHTML()); // only first element is used
// Test for wrapping multiple nodes
$c = new HtmlPageCrawler('
');
$c->filter('p')->wrap('
');
$this->assertEquals('
', $c->saveHTML());
$c = new HtmlPageCrawler('plain text node');
$c->wrap('
');
$this->assertEquals('
plain text node
', $c->ancestors()->eq(0)->saveHTML());
$c = HtmlPageCrawler::create('
');
$m = HtmlPageCrawler::create('message 1')->appendTo($c);
$m->wrap('
');
$m = HtmlPageCrawler::create('message 2')->appendTo($c);
$m->wrap('
');
$this->assertEquals('
', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::replaceWith
*/
public function testReplaceWith()
{
$c = HtmlPageCrawler::create('
');
$oldparagraphs = $c->filter('p')->replaceWith('
newtext 1
newtext 2
');
$this->assertEquals('
newtext 1
newtext 2
newtext 1
newtext 2
newtext 1
newtext 2
', $c->saveHTML());
$this->assertEquals('
Absatz 1
Absatz 2
Absatz 3
', $oldparagraphs->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::replaceAll
*/
public function testReplaceAll()
{
$c = HtmlPageCrawler::create('
');
$new = HtmlPageCrawler::create('
newtext 1
newtext 2
');
$new->replaceAll($c->filter('p'));
$this->assertEquals('
newtext 1
newtext 2
newtext 1
newtext 2
newtext 1
newtext 2
', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::wrapAll
*/
public function testWrapAll()
{
$c = HtmlPageCrawler::create('
Before
Absatz 1
Inner
Absatz 2
Absatz 3
After
');
$c->filter('p')->wrapAll('
');
$this->assertEquals('
', $c->saveHTML());
// Test for wrapping with elements that have children
$c = HtmlPageCrawler::create('
');
$c->filter('p')->wrapAll('
');
$this->assertEquals('
', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::wrapInner
*/
public function testWrapInner()
{
$c = HtmlPageCrawler::create('
');
$c->wrapInner('
');
$this->assertEquals('
', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::unwrap
*/
public function testUnwrap()
{
$c = HtmlPageCrawler::create('
');
$p = $c->filter('p');
$p->unwrap();
$this->assertEquals('
', $c->saveHTML());
}
public function testUnwrapInnerOnDOMElementException()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('DOMElement does not have a parent DOMElement node.');
$c = HtmlPageCrawler::create('
');
$p = $c->filter('div#content');
$p->unwrapInner();
$p->unwrapInner();
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::unwrapInner
*/
public function testUnwrapInner()
{
$c = HtmlPageCrawler::create('
');
$p = $c->filter('div.a');
$p->unwrapInner();
$this->assertEquals('
', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::toggleClass
*/
public function testToggleClass()
{
$c = HtmlPageCrawler::create('
');
$c->filter('div')->toggleClass('a d')->toggleClass('b');
$this->assertEquals('
', $c->saveHTML());
}
public function testRemove()
{
// remove every third td in tbody
$html = <<
| A |
B |
| 16.12.2013 |
asdf asdf |
|
| 02.12.2013 16:30 |
asdf asdf |
|
| 25.11.2013 16:30 |
asdf asdf |
|
| 18.11.2013 16:30 |
asdf asdf |
|
| 24.10.2013 16:30 |
asdf asdf |
|
| 10.10.2013 16:30 |
asdf asdf |
|
END;
$c = HtmlPageCrawler::create($html);
$this->assertEquals(1, count($c->filter('td.c23')));
$tbd = $c->filter('table > tbody > tr > td')
->reduce(
function ($c, $j) {
if (($j+1) % 3 == 0) {
return true;
}
return false;
}
);
$this->assertEquals(6, count($tbd));
$tbd->remove();
$this->assertEquals(0, count($tbd));
$this->assertEquals(0, count($c->filter('td.c23')));
}
public function testUTF8Characters()
{
$text = file_get_contents(__DIR__ . '/utf8.html');
$c = HtmlPageCrawler::create($text);
$expected =<<< END
Die Burse wurde unmittelbar (1478 bis 1482) nach der Universitätsgründung als Studentenwohnhaus und -lehranstalt errichtet. Hier lehrte der Humanist und Reformator Philipp Melanchthon bis zu seiner Berufung nach Wittenberg 1518, an ihn erinnert eine Gedenktafel. 1803 bis 1805 wurde das Gebäude im Stil des Klassizismus zum ersten Tübinger Klinikum umgebaut. Einer der ersten Patienten war Friedrich Hölderlin, der nach einer 231 Tage dauernden Behandlung am 3. Mai 1807 als unheilbar entlassen wurde.
Einst Badeanstalt vor der Stadtmauer. Wer durch das kleine Stadttor geht, hat – rückwärts gewandt – einen guten Blick auf die Stadtbefestigung mit "Pechnasen" und Spuren des alten Wehrgangs.
END;
$this->assertEquals($expected, $c->filter('p')->saveHTML());
}
public function testAttr()
{
$c = HtmlPageCrawler::create('');
$this->assertNull($c->attr('data-foo'));
$c->setAttribute('data-foo', 'bar');
$this->assertEquals('bar', $c->attr('data-foo'));
$this->assertEquals('bar', $c->getAttribute('data-foo'));
$c->removeAttribute('data-foo');
$this->assertNull($c->attr('data-foo'));
$c->setAttribute('data-foo', 'bar');
$this->assertEquals('bar', $c->attr('data-foo'));
// getAttribute is just an alias to attr() and should provide the same result
$this->assertEquals('bar', $c->getAttribute('data-foo'));
$c->removeAttr('data-foo');
$this->assertNull($c->attr('data-foo'));
}
public function testAttrOnInvalidNodeList()
{
$this->expectException(\InvalidArgumentException::class);
$c = HtmlPageCrawler::create(null);
$c->attr('data-foo');
}
public function testSetInnerHtml()
{
$html = HtmlPageCrawler::create('
Title
');
$this->assertInstanceOf('Wa72\HtmlPageDom\HtmlPageCrawler', $html->setInnerHtml('
Title
'));
$this->assertEquals('
Title
', $html->html());
// getInnerHtml is just an alias for html() and should provide the same result
$this->assertEquals('
Title
', $html->getInnerHtml());
}
public function testToString()
{
$html = HtmlPageCrawler::create('
Title
');
$this->assertEquals('
Title
', (string) $html);
}
public function testGetDOMDocument()
{
$html = HtmlPageCrawler::create('
Title
');
$this->assertInstanceOf('\DOMDocument', $html->getDOMDocument());
}
public function testAddOnCrawlerInstance()
{
$html = HtmlPageCrawler::create('
Title
');
$html->add($html);
$this->assertEquals('
Title
', (string) $html);
}
public function testReturnValues()
{
// appendTo, insertBefore, insertAfter, replaceAll should always return new Crawler objects
// see http://jquery.com/upgrade-guide/1.9/#appendto-insertbefore-insertafter-and-replaceall
$c1 = HtmlPageCrawler::create('
Headline
');
$c2 = HtmlPageCrawler::create('
1
2
3
');
$c3 = HtmlPageCrawler::create('
asdf');
$r1 = $c3->appendTo($c1);
$this->assertNotEquals(spl_object_hash($c3), spl_object_hash($r1));
$r2 = $c3->insertBefore($c1);
$this->assertNotEquals(spl_object_hash($c3), spl_object_hash($r2));
$r3 = $c3->insertAfter($c1);
$this->assertNotEquals(spl_object_hash($c3), spl_object_hash($r3));
$r4 = $c3->replaceAll($c1);
$this->assertNotEquals(spl_object_hash($c3), spl_object_hash($r4));
$r1 = $c3->appendTo($c2);
$this->assertNotEquals(spl_object_hash($c2), spl_object_hash($r1));
$r2 = $c3->insertBefore($c2);
$this->assertNotEquals(spl_object_hash($c2), spl_object_hash($r2));
$r3 = $c3->insertAfter($c2);
$this->assertNotEquals(spl_object_hash($c2), spl_object_hash($r3));
$r4 = $c3->replaceAll($c2);
$this->assertNotEquals(spl_object_hash($c2), spl_object_hash($r4));
}
public function testDisconnectedNodes()
{
// if after(), before() or replaceWith() is called on a node without parent,
// the unmodified Crawler object should be returned
//
// see http://jquery.com/upgrade-guide/1.9/#after-before-and-replacewith-with-disconnected-nodes
$c = HtmlPageCrawler::create('
abc
');
$r = HtmlPageCrawler::create('
def
');
$r1 = $c->after($r);
$this->assertEquals(spl_object_hash($r1), spl_object_hash($c));
$this->assertEquals(count($r1), count($c));
$r2 = $c->before($r);
$this->assertEquals(spl_object_hash($r2), spl_object_hash($c));
$this->assertEquals(count($r2), count($c));
$r3 = $c->replaceWith($r);
$this->assertEquals(spl_object_hash($r3), spl_object_hash($c));
$this->assertEquals(count($r3), count($c));
}
public function testClone()
{
$c = HtmlPageCrawler::create('
');
$p = $c->filter('p');
$p1 = $p->makeClone();
$this->assertNotEquals(spl_object_hash($p), spl_object_hash($p1));
$this->assertTrue($p1->hasClass('x'));
$p1->removeClass('x');
$this->assertTrue($p->hasClass('x'));
$this->assertFalse($p1->hasClass('x'));
$p->after($p1);
$this->assertEquals('
', $c->saveHTML());
}
public function testGetCombinedText()
{
$c = HtmlPageCrawler::create('
abc
def
');
$this->assertEquals('abcdef', $c->getCombinedText());
$c->setText('jklo');
$this->assertEquals('jklojklo', $c->getCombinedText());
}
public function testSetText()
{
$c = HtmlPageCrawler::create('
"
');
$this->assertEquals('"', $c->text());
$c->setText('&');
$this->assertEquals('&', $c->text());
}
public function testMagicGet()
{
// $crawler->length should give us the number of nodes in the crawler
$c = HtmlPageCrawler::create('
abc
def
');
$this->assertEquals(2, $c->length);
// not existing property throws exception
try {
$c->foo;
} catch (\Exception $e) {
$this->assertEquals('No such property foo', $e->getMessage());
return;
}
$this->fail();
}
}
================================================
FILE: Tests/HtmlPageTest.php
================================================
root = vfsStream::setup('root');
}
public function testHtmlPage()
{
$hp = new HtmlPage;
$this->assertEquals("\n
\n", $hp->__toString());
$title = 'Erste Testseite';
$hp->setTitle($title);
$this->assertEquals($title, $hp->getTitle());
$title = 'Seite "schön & gut" >> so wird\'s, süß';
$hp->setTitle($title);
$this->assertEquals($title, $hp->getTitle());
$description = 'Dies ist die erste "Testseite" >> so wird\'s, süß';
$hp->setMeta('description', $description);
$this->assertEquals($description, $hp->getMeta('description'));
$hp->removeMeta('description');
$this->assertNull($hp->getMeta('description'));
$bodycontent = '
Testcontent1
';
$body = $hp->filter('body');
$body->setInnerHtml($bodycontent);
$this->assertEquals($bodycontent, $body->html());
$this->assertEquals($bodycontent, $hp->filter('body')->html());
$content = "
Überschrift
\n
bla bla
fett
";
$hp->setHtmlById('content', $content);
// echo $hp;
$this->assertEquals($content, $hp->getElementById('content')->html());
$url = 'http://www.tuebingen.de/';
$hp->setBaseHref($url);
$this->assertEquals($url, $hp->getBaseHref());
}
public function testClone()
{
$hp = new HtmlPage;
$this->assertEquals("\n
\n", $hp->__toString());
$title = 'Erste Testseite';
$hp->setTitle($title);
$this->assertEquals($title, $hp->getTitle());
$hp2 = clone $hp;
$newtitle = 'Seitentitel neu';
$hp->setTitle($newtitle);
$this->assertEquals($title, $hp2->getTitle());
$this->assertEquals($newtitle, $hp->getTitle());
}
public function testScript()
{
$html =<<
END;
$hp = new HtmlPage($html);
$hp->getBody()->append('Script Test
');
$newhtml = $hp->save();
$expected =<<
Script Test
END;
$this->assertEquals($expected, $newhtml);
}
public function testMinify()
{
$html =<<
TEST
asdf jksdlf ajsfk
jasdf
jaksfd asdf
jasdf jaks
END;
$hp = new HtmlPage($html);
$expected = <<
TEST
asdf jksdlf ajsfk jasdf jaksfd asdf jasdf jaks
END;
$this->assertEquals($expected, $hp->minify()->save());
}
public function testIndent()
{
$html =<<
TEST
asdf jksdlf ajsfk
jasdf
jaksfd asdf
jasdf jaks
END;
$hp = new HtmlPage($html);
$expected = <<
TEST
asdf jksdlf ajsfk jasdf jaksfd asdf jasdf jaks
END;
$this->assertEquals($expected, $hp->indent()->save());
}
public function testGetCrawler()
{
$html = <<
TEST
asdf jksdlf ajsfk
jasdf
jaksfd asdf
jasdf jaks
END;
$hp = new HtmlPage($html);
$this->assertEquals('TEST
', $hp->getCrawler()->filter('h1')->saveHtml());
}
public function testGetDOMDocument()
{
$html = <<
TEST
asdf jksdlf ajsfk
jasdf
jaksfd asdf
jasdf jaks
END;
$hp = new HtmlPage($html);
$this->assertInstanceOf('\DOMDocument', $hp->getDOMDocument());
}
public function testSetTitleOnNoTitleElement()
{
$html = <<
TEST
asdf jksdlf ajsfk
jasdf
jaksfd asdf
jasdf jaks
END;
$hp = new HtmlPage($html);
$hp->setTitle('TEST');
$this->assertEquals('TEST', $hp->getTitle());
}
public function testGetTitleShouldReturnNull()
{
$html = <<
TEST
asdf jksdlf ajsfk
jasdf
jaksfd asdf
jasdf jaks
END;
$hp = new HtmlPage($html);
$this->assertNull($hp->getTitle());
}
public function testGetBaseHrefShouldReturnNull()
{
$hp = new HtmlPage('TESTHello');
$this->assertNull($hp->getBaseHref());
}
public function testGetHeadNodeShouldAddTheHeadTag()
{
$hp = new HtmlPage('Hello');
$this->assertInstanceOf('\DOMElement', $hp->getHeadNode());
$this->assertEquals('', (string) $hp->getHead());
}
public function testGetBodyNodeShouldAddTheBodyTag()
{
$hp = new HtmlPage('');
$this->assertInstanceOf('\DOMElement', $hp->getBodyNode());
$this->assertEquals('', (string) $hp->getBody());
}
public function testTrimNewlines()
{
$html = <<
TEST
END;
$this->assertEquals(' TEST ', (string) HtmlPage::trimNewlines($html));
}
public function testSaveOnFileName()
{
$hp = new HtmlPage('TEST');
$hp->save(vfsStream::url('root/save.html'));
$this->assertFileExists(vfsStream::url('root/save.html'));
}
public function testEmbeddedScriptWithHtml()
{
// PHP DOMDocument->loadHTML method tends to "eat" closing tags in html strings within script elements
// see https://stackoverflow.com/questions/24575136/domdocument-removes-html-tags-in-javascript-string
$html = <<
test
END;
$hp = new HtmlPage($html);
$this->assertEquals($html . "\n", $hp->save());
}
}
================================================
FILE: Tests/phpunit_bootstrap.php
================================================
Die Burse wurde unmittelbar (1478 bis 1482) nach der Universitätsgründung als Studentenwohnhaus und -lehranstalt errichtet. Hier lehrte der Humanist und Reformator Philipp Melanchthon bis zu seiner Berufung nach Wittenberg 1518, an ihn erinnert eine Gedenktafel. 1803 bis 1805 wurde das Gebäude im Stil des Klassizismus zum ersten Tübinger Klinikum umgebaut. Einer der ersten Patienten war Friedrich Hölderlin, der nach einer 231 Tage dauernden Behandlung am 3. Mai 1807 als unheilbar entlassen wurde.
Einst Badeanstalt vor der Stadtmauer. Wer durch das kleine Stadttor geht, hat – rückwärts gewandt – einen guten Blick auf die Stadtbefestigung mit "Pechnasen" und Spuren des alten Wehrgangs.
================================================
FILE: UPGRADE.md
================================================
Upgrade from 2.x to 3.0
-----------------------
Release 3.x is compatible only with Symfony 6, while older releases are compatible with Symfony up to 5.4.
Otherwise there are no changes in our API, so no changes should be required in your code using this lib. Just upgrade to Version 3 when you upgrade your project to Symfony 6 and all should be well.
Upgrade from 1.x to 2.0
------------------------
Several changes have been made to the public API in 2.0 in order to keep
compatibility with Symfony 4.3:
- `HtmlPageCrawler::html()` is now just the parent `Crawler::html()` and acts as *getter* only.
Setting HTML content via `HtmlPageCrawler::html($html)` is *not possible* any more,
use `HtmlPageCrawler::setInnerHtml($html)` instead
- `HtmlPageCrawler::text()` is now just the parent `Crawler::text()` and acts as *getter* only
that returns the text content from the *first* node only. For setting text content, use
`HtmlPageCrawler::setText($text)` instead.
- new method `HtmlPageCrawler::getCombinedText()` that returns the combined text from all nodes
(as jQuery's `text()` function does and previous versions of `HtmlPageCrawler::text()` did)
- `HtmlPageCrawler::attr()` is now just the parent `Crawler::attr()` and acts as *getter* only.
For setting attributes use `HtmlPageCrawler::setAttribute($name, $value)`
- removed method `HtmlPageCrawler::isDisconnected()`
__To update your code, you have to:__
- replace all calls to `$MyCrawlerInstance->html($html)` used as *setter* by `$MyCrawlerInstance->setInnerHtml($html)`
- replace all calls to `$MyCrawlerInstance->attr($name, $value)` used as *setter* by `$MyCrawlerInstance->setAttribute($name, $value)`
- replace all calls to `$MyCrawlerInstance->text($text)` used as *setter* by `$MyCrawlerInstance->setText($text)`
- replace all calls to `$MyCrawlerInstance->text()` (i.e. every call to `text()` not preceded by `first()`) by `$MyCrawlerInstance->getCombinedText()`
- replace all calls to `$MyCrawlerInstance->first()->text()` by `$MyCrawlerInstance->text()`
================================================
FILE: composer.json
================================================
{
"name":"wa72/htmlpagedom",
"description":"jQuery-inspired DOM manipulation extension for Symfony's Crawler",
"keywords":["HTML", "DOM", "Crawler"],
"homepage":"http://github.com/wasinger/htmlpagedom",
"type":"library",
"license":"MIT",
"authors":[
{
"name":"Christoph Singer",
"email":"singer@webagentur72.de",
"homepage":"http://www.webagentur72.de"
}
],
"require":{
"php":"^8.1",
"ext-dom":"*",
"ext-libxml":"*",
"symfony/polyfill-mbstring": "~1.0",
"symfony/dom-crawler":"^6.0 || ^7.0 || ^8.0",
"symfony/css-selector":"^6.0 || ^7.0 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^9",
"wa72/html-pretty-min": "~0.1",
"mikey179/vfsstream": "^1.6.10",
"scrutinizer/ocular": "^1.9",
"clean/phpdoc-md": "^0.19.3"
},
"suggest": {
"wa72/html-pretty-min": "Minify or indent HTML documents"
},
"autoload":{
"psr-4":{
"Wa72\\HtmlPageDom\\":"src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
}
}
================================================
FILE: doc/HtmlPage.md
================================================
# Wa72\HtmlPageDom\HtmlPage
This class represents a complete HTML document.
It offers convenience functions for getting and setting elements of the document
such as setTitle(), getTitle(), setMeta($name, $value), getBody().
It uses HtmlPageCrawler to navigate and manipulate the DOM tree.
## Implements:
Stringable
## Methods
| Name | Description |
|------|-------------|
|[__clone](#htmlpage__clone)||
|[__construct](#htmlpage__construct)||
|[__toString](#htmlpage__tostring)||
|[filter](#htmlpagefilter)|Filter nodes by using a CSS selector|
|[filterXPath](#htmlpagefilterxpath)|Filter nodes by XPath expression|
|[getBaseHref](#htmlpagegetbasehref)|Get the href attribute from the base tag, null if not present in document|
|[getBody](#htmlpagegetbody)|Get the document's body wrapped in a HtmlPageCrawler instance|
|[getBodyNode](#htmlpagegetbodynode)|Get the document's body as DOMElement|
|[getCrawler](#htmlpagegetcrawler)|Get a HtmlPageCrawler object containing the root node of the HTML document|
|[getDOMDocument](#htmlpagegetdomdocument)|Get a DOMDocument object for the HTML document|
|[getElementById](#htmlpagegetelementbyid)|Get an element in the document by it's id attribute|
|[getHead](#htmlpagegethead)|Get the document's HEAD section wrapped in a HtmlPageCrawler instance|
|[getHeadNode](#htmlpagegetheadnode)|Get the document's HEAD section as DOMElement|
|[getMeta](#htmlpagegetmeta)|Get the content attribute of a meta tag with the specified name attribute|
|[getTitle](#htmlpagegettitle)|Get the page title of the HTML document|
|[indent](#htmlpageindent)|indent the HTML document|
|[minify](#htmlpageminify)|minify the HTML document|
|[removeMeta](#htmlpageremovemeta)|Remove all meta tags with the specified name attribute|
|[save](#htmlpagesave)|Save this document to a HTML file or return HTML code as string|
|[setBaseHref](#htmlpagesetbasehref)|Set the base tag with href attribute set to parameter $url|
|[setHtmlById](#htmlpagesethtmlbyid)|Sets innerHTML content of an element specified by elementId|
|[setMeta](#htmlpagesetmeta)|Set a META tag with specified 'name' and 'content' attributes|
|[setTitle](#htmlpagesettitle)|Sets the page title of the HTML document|
|[trimNewlines](#htmlpagetrimnewlines)|remove newlines from string and minimize whitespace (multiple whitespace characters replaced by one space)|
### HtmlPage::__clone
**Description**
```php
__clone (void)
```
**Parameters**
`This function has no parameters.`
**Return Values**
`void`
### HtmlPage::__construct
**Description**
```php
__construct (void)
```
**Parameters**
`This function has no parameters.`
**Return Values**
`void`
### HtmlPage::__toString
**Description**
```php
__toString (void)
```
**Parameters**
`This function has no parameters.`
**Return Values**
`void`
### HtmlPage::filter
**Description**
```php
public filter (string $selector)
```
Filter nodes by using a CSS selector
**Parameters**
* `(string) $selector`
: CSS selector
**Return Values**
`\HtmlPageCrawler`
### HtmlPage::filterXPath
**Description**
```php
public filterXPath (string $xpath)
```
Filter nodes by XPath expression
**Parameters**
* `(string) $xpath`
: XPath expression
**Return Values**
`\HtmlPageCrawler`
### HtmlPage::getBaseHref
**Description**
```php
public getBaseHref (void)
```
Get the href attribute from the base tag, null if not present in document
**Parameters**
`This function has no parameters.`
**Return Values**
`null|string`
### HtmlPage::getBody
**Description**
```php
public getBody (void)
```
Get the document's body wrapped in a HtmlPageCrawler instance
**Parameters**
`This function has no parameters.`
**Return Values**
`\HtmlPageCrawler`
### HtmlPage::getBodyNode
**Description**
```php
public getBodyNode (void)
```
Get the document's body as DOMElement
**Parameters**
`This function has no parameters.`
**Return Values**
`\DOMElement`
### HtmlPage::getCrawler
**Description**
```php
public getCrawler (void)
```
Get a HtmlPageCrawler object containing the root node of the HTML document
**Parameters**
`This function has no parameters.`
**Return Values**
`\HtmlPageCrawler`
### HtmlPage::getDOMDocument
**Description**
```php
public getDOMDocument (void)
```
Get a DOMDocument object for the HTML document
**Parameters**
`This function has no parameters.`
**Return Values**
`\DOMDocument`
### HtmlPage::getElementById
**Description**
```php
public getElementById (string $id)
```
Get an element in the document by it's id attribute
**Parameters**
* `(string) $id`
**Return Values**
`\HtmlPageCrawler`
### HtmlPage::getHead
**Description**
```php
public getHead (void)
```
Get the document's HEAD section wrapped in a HtmlPageCrawler instance
**Parameters**
`This function has no parameters.`
**Return Values**
`\HtmlPageCrawler`
### HtmlPage::getHeadNode
**Description**
```php
public getHeadNode (void)
```
Get the document's HEAD section as DOMElement
**Parameters**
`This function has no parameters.`
**Return Values**
`\DOMElement`
### HtmlPage::getMeta
**Description**
```php
public getMeta (string $name)
```
Get the content attribute of a meta tag with the specified name attribute
**Parameters**
* `(string) $name`
**Return Values**
`null|string`
### HtmlPage::getTitle
**Description**
```php
public getTitle (void)
```
Get the page title of the HTML document
**Parameters**
`This function has no parameters.`
**Return Values**
`null|string`
### HtmlPage::indent
**Description**
```php
public indent (array $options)
```
indent the HTML document
**Parameters**
* `(array) $options`
: Options passed to PrettyMin::__construct()
**Return Values**
`\HtmlPage`
**Throws Exceptions**
`\Exception`
### HtmlPage::minify
**Description**
```php
public minify (array $options)
```
minify the HTML document
**Parameters**
* `(array) $options`
: Options passed to PrettyMin::__construct()
**Return Values**
`\HtmlPage`
**Throws Exceptions**
`\Exception`
### HtmlPage::removeMeta
**Description**
```php
public removeMeta (string $name)
```
Remove all meta tags with the specified name attribute
**Parameters**
* `(string) $name`
**Return Values**
`void`
### HtmlPage::save
**Description**
```php
public save (string $filename)
```
Save this document to a HTML file or return HTML code as string
**Parameters**
* `(string) $filename`
: If provided, output will be saved to this file, otherwise returned
**Return Values**
`string|void`
### HtmlPage::setBaseHref
**Description**
```php
public setBaseHref (string $url)
```
Set the base tag with href attribute set to parameter $url
**Parameters**
* `(string) $url`
**Return Values**
`void`
### HtmlPage::setHtmlById
**Description**
```php
public setHtmlById (string $elementId, string $html)
```
Sets innerHTML content of an element specified by elementId
**Parameters**
* `(string) $elementId`
* `(string) $html`
**Return Values**
`void`
### HtmlPage::setMeta
**Description**
```php
public setMeta ( $name, $content)
```
Set a META tag with specified 'name' and 'content' attributes
**Parameters**
* `() $name`
* `() $content`
**Return Values**
`void`
### HtmlPage::setTitle
**Description**
```php
public setTitle (string $title)
```
Sets the page title of the HTML document
**Parameters**
* `(string) $title`
**Return Values**
`void`
### HtmlPage::trimNewlines
**Description**
```php
public static trimNewlines (string $string)
```
remove newlines from string and minimize whitespace (multiple whitespace characters replaced by one space)
useful for cleaning up text retrieved by HtmlPageCrawler::text() (nodeValue of a DOMNode)
**Parameters**
* `(string) $string`
**Return Values**
`string`
================================================
FILE: doc/HtmlPageCrawler.md
================================================
# Wa72\HtmlPageDom\HtmlPageCrawler
Extends \Symfony\Component\DomCrawler\Crawler by adding tree manipulation functions
for HTML documents inspired by jQuery such as setInnerHtml(), css(), append(), prepend(), before(),
addClass(), removeClass()
## Implements:
Countable, IteratorAggregate, Traversable, Stringable
## Extend:
Symfony\Component\DomCrawler\Crawler
## Methods
| Name | Description |
|------|-------------|
|[__clone](#htmlpagecrawler__clone)||
|[__get](#htmlpagecrawler__get)||
|[__toString](#htmlpagecrawler__tostring)||
|[addClass](#htmlpagecrawleraddclass)|Adds the specified class(es) to each element in the set of matched elements.|
|[addHtmlFragment](#htmlpagecrawleraddhtmlfragment)||
|[after](#htmlpagecrawlerafter)|Insert content, specified by the parameter, after each element in the set of matched elements.|
|[append](#htmlpagecrawlerappend)|Insert HTML content as child nodes of each element after existing children|
|[appendTo](#htmlpagecrawlerappendto)|Insert every element in the set of matched elements to the end of the target.|
|[before](#htmlpagecrawlerbefore)|Insert content, specified by the parameter, before each element in the set of matched elements.|
|[create](#htmlpagecrawlercreate)|Get an HtmlPageCrawler object from a HTML string, DOMNode, DOMNodeList or HtmlPageCrawler|
|[css](#htmlpagecrawlercss)|Get one CSS style property of the first element or set it for all elements in the list|
|[getAttribute](#htmlpagecrawlergetattribute)|Returns the attribute value of the first node of the list.|
|[getCombinedText](#htmlpagecrawlergetcombinedtext)|Get the combined text contents of each element in the set of matched elements, including their descendants.|
|[getDOMDocument](#htmlpagecrawlergetdomdocument)|get ownerDocument of the first element|
|[getInnerHtml](#htmlpagecrawlergetinnerhtml)|Alias for Crawler::html() for naming consistency with setInnerHtml()|
|[getStyle](#htmlpagecrawlergetstyle)|get one CSS style property of the first element|
|[hasClass](#htmlpagecrawlerhasclass)|Determine whether any of the matched elements are assigned the given class.|
|[insertAfter](#htmlpagecrawlerinsertafter)|Insert every element in the set of matched elements after the target.|
|[insertBefore](#htmlpagecrawlerinsertbefore)|Insert every element in the set of matched elements before the target.|
|[isHtmlDocument](#htmlpagecrawlerishtmldocument)|checks whether the first node contains a complete html document (as opposed to a document fragment)|
|[makeClone](#htmlpagecrawlermakeclone)|Create a deep copy of the set of matched elements.|
|[makeEmpty](#htmlpagecrawlermakeempty)|Removes all child nodes and text from all nodes in set|
|[prepend](#htmlpagecrawlerprepend)|Insert content, specified by the parameter, to the beginning of each element in the set of matched elements.|
|[prependTo](#htmlpagecrawlerprependto)|Insert every element in the set of matched elements to the beginning of the target.|
|[remove](#htmlpagecrawlerremove)|Remove the set of matched elements from the DOM.|
|[removeAttr](#htmlpagecrawlerremoveattr)|Remove an attribute from each element in the set of matched elements.|
|[removeAttribute](#htmlpagecrawlerremoveattribute)|Remove an attribute from each element in the set of matched elements.|
|[removeClass](#htmlpagecrawlerremoveclass)|Remove a class from each element in the list|
|[replaceAll](#htmlpagecrawlerreplaceall)|Replace each target element with the set of matched elements.|
|[replaceWith](#htmlpagecrawlerreplacewith)|Replace each element in the set of matched elements with the provided new content and return the set of elements that was removed.|
|[saveHTML](#htmlpagecrawlersavehtml)|Get the HTML code fragment of all elements and their contents.|
|[setAttribute](#htmlpagecrawlersetattribute)|Sets an attribute on each element|
|[setInnerHtml](#htmlpagecrawlersetinnerhtml)|Set the HTML contents of each element|
|[setStyle](#htmlpagecrawlersetstyle)|set one CSS style property for all elements in the list|
|[setText](#htmlpagecrawlersettext)|Set the text contents of the matched elements.|
|[toggleClass](#htmlpagecrawlertoggleclass)|Add or remove one or more classes from each element in the set of matched elements, depending the class’s presence.|
|[unwrap](#htmlpagecrawlerunwrap)|Remove the parents of the set of matched elements from the DOM, leaving the matched elements in their place.|
|[unwrapInner](#htmlpagecrawlerunwrapinner)|Remove the matched elements, but promote the children to take their place.|
|[wrap](#htmlpagecrawlerwrap)|Wrap an HTML structure around each element in the set of matched elements|
|[wrapAll](#htmlpagecrawlerwrapall)|Wrap an HTML structure around all elements in the set of matched elements.|
|[wrapInner](#htmlpagecrawlerwrapinner)|Wrap an HTML structure around the content of each element in the set of matched elements.|
## Inherited methods
| Name | Description |
|------|-------------|
| [__construct](https://secure.php.net/manual/en/symfony\component\domcrawler\crawler.__construct.php) | - |
|add|Adds a node to the current list of nodes.|
|addContent|Adds HTML/XML content.|
|addDocument|Adds a \DOMDocument to the list of nodes.|
|addHtmlContent|Adds an HTML content to the list of nodes.|
|addNode|Adds a \DOMNode instance to the list of nodes.|
|addNodeList|Adds a \DOMNodeList to the list of nodes.|
|addNodes|Adds an array of \DOMNode instances to the list of nodes.|
|addXmlContent|Adds an XML content to the list of nodes.|
|ancestors|Returns the ancestors of the current selection.|
|attr|Returns the attribute value of the first node of the list.|
|children|Returns the children nodes of the current selection.|
|clear|Removes all the nodes.|
|closest|Return first parents (heading toward the document root) of the Element that matches the provided selector.|
| [count](https://secure.php.net/manual/en/symfony\component\domcrawler\crawler.count.php) | - |
|each|Calls an anonymous function on each node of the list.|
|eq|Returns a node given its position in the node list.|
|evaluate|Evaluates an XPath expression.|
|extract|Extracts information from the list of nodes.|
|filter|Filters the list of nodes with a CSS selector.|
|filterXPath|Filters the list of nodes with an XPath expression.|
|first|Returns the first node of the current selection.|
|form|Returns a Form object for the first node in the list.|
|getBaseHref|Returns base href.|
| [getIterator](https://secure.php.net/manual/en/symfony\component\domcrawler\crawler.getiterator.php) | - |
| [getNode](https://secure.php.net/manual/en/symfony\component\domcrawler\crawler.getnode.php) | - |
|getUri|Returns the current URI.|
|html|Returns the first node of the list as HTML.|
|image|Returns an Image object for the first node in the list.|
|images|Returns an array of Image objects for the nodes in the list.|
|innerText|Returns only the inner text that is the direct descendent of the current node, excluding any child nodes.|
|last|Returns the last node of the current selection.|
|link|Returns a Link object for the first node in the list.|
|links|Returns an array of Link objects for the nodes in the list.|
| [matches](https://secure.php.net/manual/en/symfony\component\domcrawler\crawler.matches.php) | - |
|nextAll|Returns the next siblings nodes of the current selection.|
|nodeName|Returns the node name of the first node of the list.|
| [outerHtml](https://secure.php.net/manual/en/symfony\component\domcrawler\crawler.outerhtml.php) | - |
|previousAll|Returns the previous sibling nodes of the current selection.|
|reduce|Reduces the list of nodes by calling an anonymous function.|
| [registerNamespace](https://secure.php.net/manual/en/symfony\component\domcrawler\crawler.registernamespace.php) | - |
|selectButton|Selects a button by name or alt value for images.|
|selectImage|Selects images by alt value.|
|selectLink|Selects links by name or alt value for clickable images.|
|setDefaultNamespacePrefix|Overloads a default namespace prefix to be used with XPath and CSS expressions.|
|siblings|Returns the siblings nodes of the current selection.|
|slice|Slices the list of nodes by $offset and $length.|
|text|Returns the text of the first node of the list.|
|xpathLiteral|Converts string for XPath expressions.|
### HtmlPageCrawler::__clone
**Description**
```php
__clone (void)
```
**Parameters**
`This function has no parameters.`
**Return Values**
`void`
### HtmlPageCrawler::__get
**Description**
```php
__get (void)
```
**Parameters**
`This function has no parameters.`
**Return Values**
`void`
### HtmlPageCrawler::__toString
**Description**
```php
__toString (void)
```
**Parameters**
`This function has no parameters.`
**Return Values**
`void`
### HtmlPageCrawler::addClass
**Description**
```php
public addClass (string $name)
```
Adds the specified class(es) to each element in the set of matched elements.
**Parameters**
* `(string) $name`
: One or more space-separated classes to be added to the class attribute of each matched element.
**Return Values**
`\HtmlPageCrawler`
> $this for chaining
### HtmlPageCrawler::addHtmlFragment
**Description**
```php
addHtmlFragment (void)
```
**Parameters**
`This function has no parameters.`
**Return Values**
`void`
### HtmlPageCrawler::after
**Description**
```php
public after (string|\HtmlPageCrawler|\DOMNode|\DOMNodeList $content)
```
Insert content, specified by the parameter, after each element in the set of matched elements.
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode|\DOMNodeList) $content`
**Return Values**
`\HtmlPageCrawler`
> $this for chaining
### HtmlPageCrawler::append
**Description**
```php
public append (string|\HtmlPageCrawler|\DOMNode|\DOMNodeList $content)
```
Insert HTML content as child nodes of each element after existing children
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode|\DOMNodeList) $content`
: HTML code fragment or DOMNode to append
**Return Values**
`\HtmlPageCrawler`
> $this for chaining
### HtmlPageCrawler::appendTo
**Description**
```php
public appendTo (string|\HtmlPageCrawler|\DOMNode|\DOMNodeList $element)
```
Insert every element in the set of matched elements to the end of the target.
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode|\DOMNodeList) $element`
**Return Values**
`\Wa72\HtmlPageDom\HtmlPageCrawler`
> A new Crawler object containing all elements appended to the target elements
### HtmlPageCrawler::before
**Description**
```php
public before (string|\HtmlPageCrawler|\DOMNode|\DOMNodeList $content)
```
Insert content, specified by the parameter, before each element in the set of matched elements.
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode|\DOMNodeList) $content`
**Return Values**
`\HtmlPageCrawler`
> $this for chaining
### HtmlPageCrawler::create
**Description**
```php
public static create (string|\HtmlPageCrawler|\DOMNode|\DOMNodeList|array $content)
```
Get an HtmlPageCrawler object from a HTML string, DOMNode, DOMNodeList or HtmlPageCrawler
This is the equivalent to jQuery's $() function when used for wrapping DOMNodes or creating DOMElements from HTML code.
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode|\DOMNodeList|array) $content`
**Return Values**
`\HtmlPageCrawler`
### HtmlPageCrawler::css
**Description**
```php
public css (string $key, null|string $value)
```
Get one CSS style property of the first element or set it for all elements in the list
Function is here for compatibility with jQuery; it is the same as getStyle() and setStyle()
**Parameters**
* `(string) $key`
: The name of the style property
* `(null|string) $value`
: The CSS value to set, or NULL to get the current value
**Return Values**
`\HtmlPageCrawler|string`
> If no param is provided, returns the CSS styles of the first element
### HtmlPageCrawler::getAttribute
**Description**
```php
public getAttribute (string $name)
```
Returns the attribute value of the first node of the list.
This is just an alias for attr() for naming consistency with setAttribute()
**Parameters**
* `(string) $name`
: The attribute name
**Return Values**
`string|null`
> The attribute value or null if the attribute does not exist
**Throws Exceptions**
`\InvalidArgumentException`
> When current node is empty
### HtmlPageCrawler::getCombinedText
**Description**
```php
public getCombinedText (void)
```
Get the combined text contents of each element in the set of matched elements, including their descendants.
This is what the jQuery text() function does, contrary to the Crawler::text() method that returns only
the text of the first node.
**Parameters**
`This function has no parameters.`
**Return Values**
`string`
### HtmlPageCrawler::getDOMDocument
**Description**
```php
public getDOMDocument (void)
```
get ownerDocument of the first element
**Parameters**
`This function has no parameters.`
**Return Values**
`\DOMDocument|null`
### HtmlPageCrawler::getInnerHtml
**Description**
```php
public getInnerHtml (void)
```
Alias for Crawler::html() for naming consistency with setInnerHtml()
**Parameters**
`This function has no parameters.`
**Return Values**
`string`
### HtmlPageCrawler::getStyle
**Description**
```php
public getStyle (string $key)
```
get one CSS style property of the first element
**Parameters**
* `(string) $key`
: name of the property
**Return Values**
`string|null`
> value of the property
### HtmlPageCrawler::hasClass
**Description**
```php
public hasClass (string $name)
```
Determine whether any of the matched elements are assigned the given class.
**Parameters**
* `(string) $name`
**Return Values**
`bool`
### HtmlPageCrawler::insertAfter
**Description**
```php
public insertAfter (string|\HtmlPageCrawler|\DOMNode|\DOMNodeList $element)
```
Insert every element in the set of matched elements after the target.
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode|\DOMNodeList) $element`
**Return Values**
`\Wa72\HtmlPageDom\HtmlPageCrawler`
> A new Crawler object containing all elements appended to the target elements
### HtmlPageCrawler::insertBefore
**Description**
```php
public insertBefore (string|\HtmlPageCrawler|\DOMNode|\DOMNodeList $element)
```
Insert every element in the set of matched elements before the target.
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode|\DOMNodeList) $element`
**Return Values**
`\Wa72\HtmlPageDom\HtmlPageCrawler`
> A new Crawler object containing all elements appended to the target elements
### HtmlPageCrawler::isHtmlDocument
**Description**
```php
public isHtmlDocument (void)
```
checks whether the first node contains a complete html document (as opposed to a document fragment)
**Parameters**
`This function has no parameters.`
**Return Values**
`bool`
### HtmlPageCrawler::makeClone
**Description**
```php
public makeClone (void)
```
Create a deep copy of the set of matched elements.
Equivalent to clone() in jQuery (clone is not a valid PHP function name)
**Parameters**
`This function has no parameters.`
**Return Values**
`\HtmlPageCrawler`
### HtmlPageCrawler::makeEmpty
**Description**
```php
public makeEmpty (void)
```
Removes all child nodes and text from all nodes in set
Equivalent to jQuery's empty() function which is not a valid function name in PHP
**Parameters**
`This function has no parameters.`
**Return Values**
`\HtmlPageCrawler`
> $this
### HtmlPageCrawler::prepend
**Description**
```php
public prepend (string|\HtmlPageCrawler|\DOMNode|\DOMNodeList $content)
```
Insert content, specified by the parameter, to the beginning of each element in the set of matched elements.
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode|\DOMNodeList) $content`
: HTML code fragment
**Return Values**
`\HtmlPageCrawler`
> $this for chaining
### HtmlPageCrawler::prependTo
**Description**
```php
public prependTo (string|\HtmlPageCrawler|\DOMNode|\DOMNodeList $element)
```
Insert every element in the set of matched elements to the beginning of the target.
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode|\DOMNodeList) $element`
**Return Values**
`\Wa72\HtmlPageDom\HtmlPageCrawler`
> A new Crawler object containing all elements prepended to the target elements
### HtmlPageCrawler::remove
**Description**
```php
public remove (void)
```
Remove the set of matched elements from the DOM.
(as opposed to Crawler::clear() which detaches the nodes only from Crawler
but leaves them in the DOM)
**Parameters**
`This function has no parameters.`
**Return Values**
`void`
### HtmlPageCrawler::removeAttr
**Description**
```php
public removeAttr (string $name)
```
Remove an attribute from each element in the set of matched elements.
Alias for removeAttribute for compatibility with jQuery
**Parameters**
* `(string) $name`
**Return Values**
`\HtmlPageCrawler`
### HtmlPageCrawler::removeAttribute
**Description**
```php
public removeAttribute (string $name)
```
Remove an attribute from each element in the set of matched elements.
**Parameters**
* `(string) $name`
**Return Values**
`\HtmlPageCrawler`
### HtmlPageCrawler::removeClass
**Description**
```php
public removeClass (string $name)
```
Remove a class from each element in the list
**Parameters**
* `(string) $name`
**Return Values**
`\HtmlPageCrawler`
> $this for chaining
### HtmlPageCrawler::replaceAll
**Description**
```php
public replaceAll (string|\HtmlPageCrawler|\DOMNode|\DOMNodeList $element)
```
Replace each target element with the set of matched elements.
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode|\DOMNodeList) $element`
**Return Values**
`\Wa72\HtmlPageDom\HtmlPageCrawler`
> A new Crawler object containing all elements appended to the target elements
### HtmlPageCrawler::replaceWith
**Description**
```php
public replaceWith (string|\HtmlPageCrawler|\DOMNode|\DOMNodeList $content)
```
Replace each element in the set of matched elements with the provided new content and return the set of elements that was removed.
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode|\DOMNodeList) $content`
**Return Values**
`\Wa72\HtmlPageDom\HtmlPageCrawler`
> $this for chaining
### HtmlPageCrawler::saveHTML
**Description**
```php
public saveHTML (void)
```
Get the HTML code fragment of all elements and their contents.
If the first node contains a complete HTML document return only
the full code of this document.
**Parameters**
`This function has no parameters.`
**Return Values**
`string`
> HTML code (fragment)
### HtmlPageCrawler::setAttribute
**Description**
```php
public setAttribute (string $name, string $value)
```
Sets an attribute on each element
**Parameters**
* `(string) $name`
* `(string) $value`
**Return Values**
`\HtmlPageCrawler`
> $this for chaining
### HtmlPageCrawler::setInnerHtml
**Description**
```php
public setInnerHtml (string|\HtmlPageCrawler|\DOMNode|\DOMNodeList $content)
```
Set the HTML contents of each element
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode|\DOMNodeList) $content`
: HTML code fragment
**Return Values**
`\HtmlPageCrawler`
> $this for chaining
### HtmlPageCrawler::setStyle
**Description**
```php
public setStyle (string $key, string $value)
```
set one CSS style property for all elements in the list
**Parameters**
* `(string) $key`
: name of the property
* `(string) $value`
: value of the property
**Return Values**
`\HtmlPageCrawler`
> $this for chaining
### HtmlPageCrawler::setText
**Description**
```php
public setText (string $text)
```
Set the text contents of the matched elements.
**Parameters**
* `(string) $text`
**Return Values**
`\HtmlPageCrawler`
### HtmlPageCrawler::toggleClass
**Description**
```php
public toggleClass (string $classname)
```
Add or remove one or more classes from each element in the set of matched elements, depending the class’s presence.
**Parameters**
* `(string) $classname`
: One or more classnames separated by spaces
**Return Values**
`\Wa72\HtmlPageDom\HtmlPageCrawler`
> $this for chaining
### HtmlPageCrawler::unwrap
**Description**
```php
public unwrap (void)
```
Remove the parents of the set of matched elements from the DOM, leaving the matched elements in their place.
**Parameters**
`This function has no parameters.`
**Return Values**
`\Wa72\HtmlPageDom\HtmlPageCrawler`
> $this for chaining
### HtmlPageCrawler::unwrapInner
**Description**
```php
public unwrapInner (void)
```
Remove the matched elements, but promote the children to take their place.
**Parameters**
`This function has no parameters.`
**Return Values**
`\Wa72\HtmlPageDom\HtmlPageCrawler`
> $this for chaining
### HtmlPageCrawler::wrap
**Description**
```php
public wrap (string|\HtmlPageCrawler|\DOMNode $wrappingElement)
```
Wrap an HTML structure around each element in the set of matched elements
The HTML structure must contain only one root node, e.g.:
Works:
Does not work:
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode) $wrappingElement`
**Return Values**
`\HtmlPageCrawler`
> $this for chaining
### HtmlPageCrawler::wrapAll
**Description**
```php
public wrapAll (string|\HtmlPageCrawler|\DOMNode|\DOMNodeList $content)
```
Wrap an HTML structure around all elements in the set of matched elements.
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode|\DOMNodeList) $content`
**Return Values**
`\Wa72\HtmlPageDom\HtmlPageCrawler`
> $this for chaining
**Throws Exceptions**
`\LogicException`
### HtmlPageCrawler::wrapInner
**Description**
```php
public wrapInner (string|\HtmlPageCrawler|\DOMNode|\DOMNodeList $content)
```
Wrap an HTML structure around the content of each element in the set of matched elements.
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode|\DOMNodeList) $content`
**Return Values**
`\Wa72\HtmlPageDom\HtmlPageCrawler`
> $this for chaining
================================================
FILE: doc/README.md
================================================
# Wa72\HtmlPageDom
* [HtmlPage](HtmlPage.md)
* [HtmlPageCrawler](HtmlPageCrawler.md)
================================================
FILE: phpunit.xml.dist
================================================
./src/
./Tests/
================================================
FILE: src/Helpers.php
================================================
value pairs of CSS properties
*/
public static function cssStringToArray($css)
{
$statements = explode(';', preg_replace('/\s+/s', ' ', $css));
$styles = array();
foreach ($statements as $statement) {
$statement = trim($statement);
if ('' === $statement) {
continue;
}
$p = strpos($statement, ':');
if ($p <= 0) {
continue;
} // invalid statement, just ignore it
$key = trim(substr($statement, 0, $p));
$value = trim(substr($statement, $p + 1));
$styles[$key] = $value;
}
return $styles;
}
/**
* Convert CSS name->value array to string
*
* @param array $array name=>value pairs of CSS properties
* @return string list of CSS properties separated by ;
*/
public static function cssArrayToString($array)
{
$styles = '';
foreach ($array as $key => $value) {
$styles .= $key . ': ' . $value . ';';
}
return $styles;
}
/**
* Helper function for getting a body element
* from an HTML fragment
*
* @param string $html A fragment of HTML code
* @param string $charset
* @return \DOMNode The body node containing child nodes created from the HTML fragment
*/
public static function getBodyNodeFromHtmlFragment($html, $charset = 'UTF-8')
{
$html = '' . $html . '';
$d = self::loadHtml($html, $charset);
return $d->getElementsByTagName('body')->item(0);
}
public static function loadHtml(string $html, $charset = 'UTF-8'): \DOMDocument
{
return self::parseXhtml($html, $charset);
}
/**
* Function originally taken from Symfony\Component\DomCrawler\Crawler
* (c) Fabien Potencier
* License: MIT
*/
private static function parseXhtml(string $htmlContent, string $charset = 'UTF-8'): \DOMDocument
{
$htmlContent = self::convertToHtmlEntities($htmlContent, $charset);
$internalErrors = libxml_use_internal_errors(true);
$dom = new \DOMDocument('1.0', $charset);
$dom->validateOnParse = true;
if ('' !== trim($htmlContent)) {
// PHP DOMDocument->loadHTML method tends to "eat" closing tags in html strings within script elements
// Option LIBXML_SCHEMA_CREATE seems to prevent this
// see https://stackoverflow.com/questions/24575136/domdocument-removes-html-tags-in-javascript-string
@$dom->loadHTML($htmlContent, \LIBXML_SCHEMA_CREATE);
}
libxml_use_internal_errors($internalErrors);
return $dom;
}
/**
* Converts charset to HTML-entities to ensure valid parsing.
* Function taken from Symfony\Component\DomCrawler\Crawler
* (c) Fabien Potencier
* License: MIT
*/
private static function convertToHtmlEntities(string $htmlContent, string $charset = 'UTF-8'): string
{
set_error_handler(function () { throw new \Exception(); });
try {
return mb_encode_numericentity($htmlContent, [0x80, 0x10FFFF, 0, 0x1FFFFF], $charset);
} catch (\Exception|\ValueError) {
try {
$htmlContent = iconv($charset, 'UTF-8', $htmlContent);
$htmlContent = mb_encode_numericentity($htmlContent, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8');
} catch (\Exception|\ValueError) {
}
return $htmlContent;
} finally {
restore_error_handler();
}
}
}
================================================
FILE: src/HtmlPage.php
================================================
charset = $charset;
$this->url = $url;
if ($content == '') {
$content = '';
}
$this->dom = Helpers::loadHtml($content, $charset);
$this->crawler = new HtmlPageCrawler($this->dom);
}
/**
* Get a HtmlPageCrawler object containing the root node of the HTML document
*
* @return HtmlPageCrawler
*/
public function getCrawler()
{
return $this->crawler;
}
/**
* Get a DOMDocument object for the HTML document
*
* @return \DOMDocument
*/
public function getDOMDocument()
{
return $this->dom;
}
/**
* Sets the page title of the HTML document
*
* @param string $title
*/
public function setTitle($title)
{
$t = $this->dom->getElementsByTagName('title')->item(0);
if ($t == null) {
$t = $this->dom->createElement('title');
$this->getHeadNode()->appendChild($t);
}
$t->nodeValue = htmlspecialchars($title);
}
/**
* Get the page title of the HTML document
*
* @return null|string
*/
public function getTitle()
{
$t = $this->dom->getElementsByTagName('title')->item(0);
if ($t == null) {
return null;
} else {
return $t->nodeValue;
}
}
/**
* Set a META tag with specified 'name' and 'content' attributes
*
* @TODO: add support for multiple meta tags with the same name but different languages
*
* @param $name
* @param $content
*/
public function setMeta($name, $content)
{
$c = $this->filterXPath('descendant-or-self::meta[@name = \'' . $name . '\']');
if (count($c) == 0) {
$node = $this->dom->createElement('meta');
$node->setAttribute('name', $name);
$this->getHeadNode()->appendChild($node);
$c->addNode($node);
}
$c->setAttribute('content', $content);
}
/**
* Remove all meta tags with the specified name attribute
*
* @param string $name
*/
public function removeMeta($name)
{
$meta = $this->filterXPath('descendant-or-self::meta[@name = \'' . $name . '\']');
$meta->remove();
}
/**
* Get the content attribute of a meta tag with the specified name attribute
*
* @param string $name
* @return null|string
*/
public function getMeta($name)
{
$node = $this->filterXPath('descendant-or-self::meta[@name = \'' . $name . '\']')->getNode(0);
if ($node instanceof \DOMElement) {
return $node->getAttribute('content');
} else {
return null;
}
}
/**
* Set the base tag with href attribute set to parameter $url
*
* @param string $url
*/
public function setBaseHref($url)
{
$node = $this->filterXPath('descendant-or-self::base')->getNode(0);
if ($node == null) {
$node = $this->dom->createElement('base');
$this->getHeadNode()->appendChild($node);
}
$node->setAttribute('href', $url);
}
/**
* Get the href attribute from the base tag, null if not present in document
*
* @return null|string
*/
public function getBaseHref()
{
$node = $this->filterXPath('descendant-or-self::base')->getNode(0);
if ($node instanceof \DOMElement) {
return $node->getAttribute('href');
} else {
return null;
}
}
/**
* Sets innerHTML content of an element specified by elementId
*
* @param string $elementId
* @param string $html
*/
public function setHtmlById($elementId, $html)
{
$this->getElementById($elementId)->setInnerHtml($html);
}
/**
* Get the document's HEAD section as DOMElement
*
* @return \DOMElement
*/
public function getHeadNode()
{
$head = $this->dom->getElementsByTagName('head')->item(0);
if ($head == null) {
$head = $this->dom->createElement('head');
$head = $this->dom->documentElement->insertBefore($head, $this->getBodyNode());
}
return $head;
}
/**
* Get the document's body as DOMElement
*
* @return \DOMElement
*/
public function getBodyNode()
{
$body = $this->dom->getElementsByTagName('body')->item(0);
if ($body == null) {
$body = $this->dom->createElement('body');
$body = $this->dom->documentElement->appendChild($body);
}
return $body;
}
/**
* Get the document's HEAD section wrapped in a HtmlPageCrawler instance
*
* @return HtmlPageCrawler
*/
public function getHead()
{
return new HtmlPageCrawler($this->getHeadNode());
}
/**
* Get the document's body wrapped in a HtmlPageCrawler instance
*
* @return HtmlPageCrawler
*/
public function getBody()
{
return new HtmlPageCrawler($this->getBodyNode());
}
public function __toString()
{
return $this->dom->saveHTML();
}
/**
* Save this document to a HTML file or return HTML code as string
*
* @param string $filename If provided, output will be saved to this file, otherwise returned
* @return string|void
*/
public function save($filename = '')
{
if ($filename != '') {
file_put_contents($filename, (string) $this);
return;
} else {
return (string) $this;
}
}
/**
* Get an element in the document by it's id attribute
*
* @param string $id
* @return HtmlPageCrawler
*/
public function getElementById($id)
{
return $this->filterXPath('descendant-or-self::*[@id = \'' . $id . '\']');
}
/**
* Filter nodes by using a CSS selector
*
* @param string $selector CSS selector
* @return HtmlPageCrawler
*/
public function filter($selector)
{
//echo "\n" . CssSelector::toXPath($selector) . "\n";
return $this->crawler->filter($selector);
}
/**
* Filter nodes by XPath expression
*
* @param string $xpath XPath expression
* @return HtmlPageCrawler
*/
public function filterXPath($xpath)
{
return $this->crawler->filterXPath($xpath);
}
/**
* remove newlines from string and minimize whitespace (multiple whitespace characters replaced by one space)
*
* useful for cleaning up text retrieved by HtmlPageCrawler::text() (nodeValue of a DOMNode)
*
* @param string $string
* @return string
*/
public static function trimNewlines($string)
{
return Helpers::trimNewlines($string);
}
public function __clone()
{
$this->dom = $this->dom->cloneNode(true);
$this->crawler = new HtmlPageCrawler($this->dom);
}
/**
* minify the HTML document
*
* @param array $options Options passed to PrettyMin::__construct()
* @return HtmlPage
* @throws \Exception
*/
public function minify(array $options = array())
{
if (!class_exists('Wa72\\HtmlPrettymin\\PrettyMin')) {
throw new \Exception('Function minify needs composer package wa72/html-pretty-min');
}
$pm = new PrettyMin($options);
$pm->load($this->dom)->minify();
return $this;
}
/**
* indent the HTML document
*
* @param array $options Options passed to PrettyMin::__construct()
* @return HtmlPage
* @throws \Exception
*/
public function indent(array $options = array())
{
if (!class_exists('Wa72\\HtmlPrettymin\\PrettyMin')) {
throw new \Exception('Function indent needs composer package wa72/html-pretty-min');
}
$pm = new PrettyMin($options);
$pm->load($this->dom)->indent();
return $this;
}
}
================================================
FILE: src/HtmlPageCrawler.php
================================================
getAttribute('class'));
$found = false;
$count = count($classes);
for ($i = 0; $i < $count; $i++) {
if ($classes[$i] == $name) {
$found = true;
}
}
if (!$found) {
$classes[] = $name;
$node->setAttribute('class', trim(join(' ', $classes)));
}
}
}
return $this;
}
/**
* Insert content, specified by the parameter, after each element in the set of matched elements.
*
* @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content
* @return HtmlPageCrawler $this for chaining
* @api
*/
public function after($content)
{
$content = self::create($content);
$newnodes = array();
foreach ($this as $i => $node) {
/** @var \DOMNode $node */
$refnode = $node->nextSibling;
foreach ($content as $newnode) {
/** @var \DOMNode $newnode */
$newnode = static::importNewnode($newnode, $node, $i);
if ($refnode === null) {
$node->parentNode->appendChild($newnode);
} else {
$node->parentNode->insertBefore($newnode, $refnode);
}
$newnodes[] = $newnode;
}
}
$content->clear();
$content->add($newnodes);
return $this;
}
/**
* Insert HTML content as child nodes of each element after existing children
*
* @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content HTML code fragment or DOMNode to append
* @return HtmlPageCrawler $this for chaining
* @api
*/
public function append($content)
{
$content = self::create($content);
$newnodes = array();
foreach ($this as $i => $node) {
/** @var \DOMNode $node */
foreach ($content as $newnode) {
/** @var \DOMNode $newnode */
$newnode = static::importNewnode($newnode, $node, $i);
$node->appendChild($newnode);
$newnodes[] = $newnode;
}
}
$content->clear();
$content->add($newnodes);
return $this;
}
/**
* Insert every element in the set of matched elements to the end of the target.
*
* @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $element
* @return \Wa72\HtmlPageDom\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements
* @api
*/
public function appendTo($element)
{
$e = self::create($element);
$newnodes = array();
foreach ($e as $i => $node) {
/** @var \DOMNode $node */
foreach ($this as $newnode) {
/** @var \DOMNode $newnode */
if ($node !== $newnode) {
$newnode = static::importNewnode($newnode, $node, $i);
$node->appendChild($newnode);
}
$newnodes[] = $newnode;
}
}
return self::create($newnodes);
}
/**
* Sets an attribute on each element
*
* @param string $name
* @param string $value
* @return HtmlPageCrawler $this for chaining
* @api
*/
public function setAttribute($name, $value)
{
foreach ($this as $node) {
if ($node instanceof \DOMElement) {
/** @var \DOMElement $node */
$node->setAttribute($name, $value);
}
}
return $this;
}
/**
* Returns the attribute value of the first node of the list.
* This is just an alias for attr() for naming consistency with setAttribute()
*
* @param string $name The attribute name
* @return string|null The attribute value or null if the attribute does not exist
* @throws \InvalidArgumentException When current node is empty
*/
public function getAttribute($name)
{
return parent::attr($name);
}
/**
* Insert content, specified by the parameter, before each element in the set of matched elements.
*
* @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content
* @return HtmlPageCrawler $this for chaining
* @api
*/
public function before($content)
{
$content = self::create($content);
$newnodes = array();
foreach ($this as $i => $node) {
/** @var \DOMNode $node */
foreach ($content as $newnode) {
/** @var \DOMNode $newnode */
if ($node !== $newnode) {
$newnode = static::importNewnode($newnode, $node, $i);
$node->parentNode->insertBefore($newnode, $node);
$newnodes[] = $newnode;
}
}
}
$content->clear();
$content->add($newnodes);
return $this;
}
/**
* Create a deep copy of the set of matched elements.
*
* Equivalent to clone() in jQuery (clone is not a valid PHP function name)
*
* @return HtmlPageCrawler
* @api
*/
public function makeClone()
{
return clone $this;
}
public function __clone()
{
$newnodes = array();
foreach ($this as $node) {
/** @var \DOMNode $node */
$newnodes[] = $node->cloneNode(true);
}
$this->clear();
$this->add($newnodes);
}
/**
* Get one CSS style property of the first element or set it for all elements in the list
*
* Function is here for compatibility with jQuery; it is the same as getStyle() and setStyle()
*
* @see HtmlPageCrawler::getStyle()
* @see HtmlPageCrawler::setStyle()
*
* @param string $key The name of the style property
* @param null|string $value The CSS value to set, or NULL to get the current value
* @return HtmlPageCrawler|string If no param is provided, returns the CSS styles of the first element
* @api
*/
public function css($key, $value = null)
{
if (null === $value) {
return $this->getStyle($key);
} else {
return $this->setStyle($key, $value);
}
}
/**
* get one CSS style property of the first element
*
* @param string $key name of the property
* @return string|null value of the property
*/
public function getStyle($key)
{
$styles = Helpers::cssStringToArray($this->getAttribute('style'));
return (isset($styles[$key]) ? $styles[$key] : null);
}
/**
* set one CSS style property for all elements in the list
*
* @param string $key name of the property
* @param string $value value of the property
* @return HtmlPageCrawler $this for chaining
*/
public function setStyle($key, $value)
{
foreach ($this as $node) {
if ($node instanceof \DOMElement) {
/** @var \DOMElement $node */
$styles = Helpers::cssStringToArray($node->getAttribute('style'));
if ($value != '') {
$styles[$key] = $value;
} elseif (isset($styles[$key])) {
unset($styles[$key]);
}
$node->setAttribute('style', Helpers::cssArrayToString($styles));
}
}
return $this;
}
/**
* Removes all child nodes and text from all nodes in set
*
* Equivalent to jQuery's empty() function which is not a valid function name in PHP
* @return HtmlPageCrawler $this
* @api
*/
public function makeEmpty()
{
foreach ($this as $node) {
$node->nodeValue = '';
}
return $this;
}
/**
* Determine whether any of the matched elements are assigned the given class.
*
* @param string $name
* @return bool
* @api
*/
public function hasClass($name)
{
foreach ($this as $node) {
if ($node instanceof \DOMElement && $class = $node->getAttribute('class')) {
$classes = preg_split('/\s+/s', $class);
if (in_array($name, $classes)) {
return true;
}
}
}
return false;
}
/**
* Set the HTML contents of each element
*
* @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content HTML code fragment
* @return HtmlPageCrawler $this for chaining
* @api
*/
public function setInnerHtml($content)
{
$content = self::create($content);
foreach ($this as $node) {
$node->nodeValue = '';
foreach ($content as $newnode) {
/** @var \DOMNode $node */
/** @var \DOMNode $newnode */
$newnode = static::importNewnode($newnode, $node);
$node->appendChild($newnode);
}
}
return $this;
}
/**
* Alias for Crawler::html() for naming consistency with setInnerHtml()
*
* @return string
* @api
*/
public function getInnerHtml()
{
return parent::html();
}
/**
* Insert every element in the set of matched elements after the target.
*
* @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $element
* @return \Wa72\HtmlPageDom\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements
* @api
*/
public function insertAfter($element)
{
$e = self::create($element);
$newnodes = array();
foreach ($e as $i => $node) {
/** @var \DOMNode $node */
$refnode = $node->nextSibling;
foreach ($this as $newnode) {
/** @var \DOMNode $newnode */
$newnode = static::importNewnode($newnode, $node, $i);
if ($refnode === null) {
$node->parentNode->appendChild($newnode);
} else {
$node->parentNode->insertBefore($newnode, $refnode);
}
$newnodes[] = $newnode;
}
}
return self::create($newnodes);
}
/**
* Insert every element in the set of matched elements before the target.
*
* @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $element
* @return \Wa72\HtmlPageDom\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements
* @api
*/
public function insertBefore($element)
{
$e = self::create($element);
$newnodes = array();
foreach ($e as $i => $node) {
/** @var \DOMNode $node */
foreach ($this as $newnode) {
/** @var \DOMNode $newnode */
$newnode = static::importNewnode($newnode, $node, $i);
if ($newnode !== $node) {
$node->parentNode->insertBefore($newnode, $node);
}
$newnodes[] = $newnode;
}
}
return self::create($newnodes);
}
/**
* Insert content, specified by the parameter, to the beginning of each element in the set of matched elements.
*
* @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content HTML code fragment
* @return HtmlPageCrawler $this for chaining
* @api
*/
public function prepend($content)
{
$content = self::create($content);
$newnodes = array();
foreach ($this as $i => $node) {
$refnode = $node->firstChild;
/** @var \DOMNode $node */
foreach ($content as $newnode) {
/** @var \DOMNode $newnode */
$newnode = static::importNewnode($newnode, $node, $i);
if ($refnode === null) {
$node->appendChild($newnode);
} else if ($refnode !== $newnode) {
$node->insertBefore($newnode, $refnode);
}
$newnodes[] = $newnode;
}
}
$content->clear();
$content->add($newnodes);
return $this;
}
/**
* Insert every element in the set of matched elements to the beginning of the target.
*
* @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $element
* @return \Wa72\HtmlPageDom\HtmlPageCrawler A new Crawler object containing all elements prepended to the target elements
* @api
*/
public function prependTo($element)
{
$e = self::create($element);
$newnodes = array();
foreach ($e as $i => $node) {
$refnode = $node->firstChild;
/** @var \DOMNode $node */
foreach ($this as $newnode) {
/** @var \DOMNode $newnode */
$newnode = static::importNewnode($newnode, $node, $i);
if ($newnode !== $node) {
if ($refnode === null) {
$node->appendChild($newnode);
} else {
$node->insertBefore($newnode, $refnode);
}
}
$newnodes[] = $newnode;
}
}
return self::create($newnodes);
}
/**
* Remove the set of matched elements from the DOM.
*
* (as opposed to Crawler::clear() which detaches the nodes only from Crawler
* but leaves them in the DOM)
*
* @api
*/
public function remove()
{
foreach ($this as $node) {
/**
* @var \DOMNode $node
*/
if ($node->parentNode instanceof \DOMElement) {
$node->parentNode->removeChild($node);
}
}
$this->clear();
}
/**
* Remove an attribute from each element in the set of matched elements.
*
* Alias for removeAttribute for compatibility with jQuery
*
* @param string $name
* @return HtmlPageCrawler
* @api
*/
public function removeAttr($name)
{
return $this->removeAttribute($name);
}
/**
* Remove an attribute from each element in the set of matched elements.
*
* @param string $name
* @return HtmlPageCrawler
*/
public function removeAttribute($name)
{
foreach ($this as $node) {
if ($node instanceof \DOMElement) {
/** @var \DOMElement $node */
if ($node->hasAttribute($name)) {
$node->removeAttribute($name);
}
}
}
return $this;
}
/**
* Remove a class from each element in the list
*
* @param string $name
* @return HtmlPageCrawler $this for chaining
* @api
*/
public function removeClass($name)
{
foreach ($this as $node) {
if ($node instanceof \DOMElement) {
/** @var \DOMElement $node */
$classes = preg_split('/\s+/s', $node->getAttribute('class'));
$count = count($classes);
for ($i = 0; $i < $count; $i++) {
if ($classes[$i] == $name) {
unset($classes[$i]);
}
}
$node->setAttribute('class', trim(join(' ', $classes)));
}
}
return $this;
}
/**
* Replace each target element with the set of matched elements.
*
* @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $element
* @return \Wa72\HtmlPageDom\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements
* @api
*/
public function replaceAll($element)
{
$e = self::create($element);
$newnodes = array();
foreach ($e as $i => $node) {
/** @var \DOMNode $node */
$parent = $node->parentNode;
$refnode = $node->nextSibling;
foreach ($this as $j => $newnode) {
/** @var \DOMNode $newnode */
$newnode = static::importNewnode($newnode, $node, $i);
if ($j == 0) {
$parent->replaceChild($newnode, $node);
} else {
$parent->insertBefore($newnode, $refnode);
}
$newnodes[] = $newnode;
}
}
return self::create($newnodes);
}
/**
* Replace each element in the set of matched elements with the provided new content and return the set of elements that was removed.
*
* @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content
* @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
* @api
*/
public function replaceWith($content)
{
$content = self::create($content);
$newnodes = array();
foreach ($this as $i => $node) {
/** @var \DOMNode $node */
$parent = $node->parentNode;
$refnode = $node->nextSibling;
foreach ($content as $j => $newnode) {
/** @var \DOMNode $newnode */
$newnode = static::importNewnode($newnode, $node, $i);
if ($j == 0) {
$parent->replaceChild($newnode, $node);
} else {
$parent->insertBefore($newnode, $refnode);
}
$newnodes[] = $newnode;
}
}
$content->clear();
$content->add($newnodes);
return $this;
}
/**
* Get the combined text contents of each element in the set of matched elements, including their descendants.
* This is what the jQuery text() function does, contrary to the Crawler::text() method that returns only
* the text of the first node.
*
* @return string
* @api
*/
public function getCombinedText()
{
$text = '';
foreach ($this as $node) {
/** @var \DOMNode $node */
$text .= $node->nodeValue;
}
return $text;
}
/**
* Set the text contents of the matched elements.
*
* @param string $text
* @return HtmlPageCrawler
* @api
*/
public function setText($text)
{
$text = htmlspecialchars($text);
foreach ($this as $node) {
/** @var \DOMNode $node */
$node->nodeValue = $text;
}
return $this;
}
/**
* Add or remove one or more classes from each element in the set of matched elements, depending the class’s presence.
*
* @param string $classname One or more classnames separated by spaces
* @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
* @api
*/
public function toggleClass($classname)
{
$classes = explode(' ', $classname);
foreach ($this as $i => $node) {
$c = self::create($node);
/** @var \DOMNode $node */
foreach ($classes as $class) {
if ($c->hasClass($class)) {
$c->removeClass($class);
} else {
$c->addClass($class);
}
}
}
return $this;
}
/**
* Remove the parents of the set of matched elements from the DOM, leaving the matched elements in their place.
*
* @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
* @api
*/
public function unwrap()
{
$parents = array();
foreach($this as $i => $node) {
$parents[] = $node->parentNode;
}
self::create($parents)->unwrapInner();
return $this;
}
/**
* Remove the matched elements, but promote the children to take their place.
*
* @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
* @api
*/
public function unwrapInner()
{
foreach($this as $i => $node) {
if (!$node->parentNode instanceof \DOMElement) {
throw new \InvalidArgumentException('DOMElement does not have a parent DOMElement node.');
}
/** @var \DOMNode[] $children */
$children = iterator_to_array($node->childNodes);
foreach ($children as $child) {
$node->parentNode->insertBefore($child, $node);
}
$node->parentNode->removeChild($node);
}
}
/**
* Wrap an HTML structure around each element in the set of matched elements
*
* The HTML structure must contain only one root node, e.g.:
* Works:
* Does not work:
*
* @param string|HtmlPageCrawler|\DOMNode $wrappingElement
* @return HtmlPageCrawler $this for chaining
* @api
*/
public function wrap($wrappingElement)
{
$content = self::create($wrappingElement);
$newnodes = array();
foreach ($this as $i => $node) {
/** @var \DOMNode $node */
$newnode = $content->getNode(0);
/** @var \DOMNode $newnode */
// $newnode = static::importNewnode($newnode, $node, $i);
if ($newnode->ownerDocument !== $node->ownerDocument) {
$newnode = $node->ownerDocument->importNode($newnode, true);
} else {
if ($i > 0) {
$newnode = $newnode->cloneNode(true);
}
}
$oldnode = $node->parentNode->replaceChild($newnode, $node);
while ($newnode->hasChildNodes()) {
$elementFound = false;
foreach ($newnode->childNodes as $child) {
if ($child instanceof \DOMElement) {
$newnode = $child;
$elementFound = true;
break;
}
}
if (!$elementFound) {
break;
}
}
$newnode->appendChild($oldnode);
$newnodes[] = $newnode;
}
$content->clear();
$content->add($newnodes);
return $this;
}
/**
* Wrap an HTML structure around all elements in the set of matched elements.
*
* @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content
* @throws \LogicException
* @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
* @api
*/
public function wrapAll($content)
{
$content = self::create($content);
$parent = $this->getNode(0)->parentNode;
foreach ($this as $i => $node) {
/** @var \DOMNode $node */
if ($node->parentNode !== $parent) {
throw new \LogicException('Nodes to be wrapped with wrapAll() must all have the same parent');
}
}
$newnode = $content->getNode(0);
/** @var \DOMNode $newnode */
$newnode = static::importNewnode($newnode, $parent);
$newnode = $parent->insertBefore($newnode,$this->getNode(0));
$content->clear();
$content->add($newnode);
while ($newnode->hasChildNodes()) {
$elementFound = false;
foreach ($newnode->childNodes as $child) {
if ($child instanceof \DOMElement) {
$newnode = $child;
$elementFound = true;
break;
}
}
if (!$elementFound) {
break;
}
}
foreach ($this as $i => $node) {
/** @var \DOMNode $node */
$newnode->appendChild($node);
}
return $this;
}
/**
* Wrap an HTML structure around the content of each element in the set of matched elements.
*
* @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content
* @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
* @api
*/
public function wrapInner($content)
{
foreach ($this as $i => $node) {
/** @var \DOMNode $node */
self::create($node->childNodes)->wrapAll($content);
}
return $this;
}
/**
* Get the HTML code fragment of all elements and their contents.
*
* If the first node contains a complete HTML document return only
* the full code of this document.
*
* @return string HTML code (fragment)
* @api
*/
public function saveHTML()
{
if ($this->isHtmlDocument()) {
return $this->getDOMDocument()->saveHTML();
} else {
$doc = new \DOMDocument('1.0', 'UTF-8');
$root = $doc->appendChild($doc->createElement('_root'));
foreach ($this as $node) {
$root->appendChild($doc->importNode($node, true));
}
$html = trim($doc->saveHTML());
return preg_replace('@^<'.self::FRAGMENT_ROOT_TAGNAME.'[^>]*>|'.self::FRAGMENT_ROOT_TAGNAME.'>$@', '', $html);
}
}
public function __toString()
{
return $this->saveHTML();
}
/**
* checks whether the first node contains a complete html document
* (as opposed to a document fragment)
*
* @return boolean
*/
public function isHtmlDocument()
{
$node = $this->getNode(0);
if ($node instanceof \DOMElement
&& $node->ownerDocument instanceof \DOMDocument
&& $node->ownerDocument->documentElement === $node
&& $node->nodeName == 'html'
) {
return true;
} else {
return false;
}
}
/**
* get ownerDocument of the first element
*
* @return \DOMDocument|null
*/
public function getDOMDocument()
{
$node = $this->getNode(0);
$r = null;
if ($node instanceof \DOMElement
&& $node->ownerDocument instanceof \DOMDocument
) {
$r = $node->ownerDocument;
}
return $r;
}
/**
* Filters the list of nodes with a CSS selector.
*
* @param string $selector
* @return HtmlPageCrawler
*/
public function filter(string $selector): static
{
return parent::filter($selector);
}
/**
* Filters the list of nodes with an XPath expression.
*
* @param string $xpath An XPath expression
*
* @return HtmlPageCrawler A new instance of Crawler with the filtered list of nodes
*
* @api
*/
public function filterXPath($xpath): static
{
return parent::filterXPath($xpath);
}
/**
* Adds HTML/XML content to the HtmlPageCrawler object (but not to the DOM of an already attached node).
*
* Function overriden from Crawler because HTML fragments are always added as complete documents there
*
*
* @param string $content A string to parse as HTML/XML
* @param null|string $type The content type of the string
*
* @return null|void
*/
public function addContent($content, $type = null): void
{
if (empty($type)) {
$type = 'text/html;charset=UTF-8';
}
if (substr($type, 0, 9) == 'text/html' && !preg_match('/]*>/i', $content)) {
// string contains no Tag => no complete document but an HTML fragment!
$this->addHtmlFragment($content);
} else {
parent::addContent($content, $type);
}
}
public function addHtmlFragment($content, $charset = 'UTF-8')
{
$d = new \DOMDocument('1.0', $charset);
$d->preserveWhiteSpace = false;
$root = $d->appendChild($d->createElement(self::FRAGMENT_ROOT_TAGNAME));
$bodynode = Helpers::getBodyNodeFromHtmlFragment($content, $charset);
foreach ($bodynode->childNodes as $child) {
$inode = $root->appendChild($d->importNode($child, true));
if ($inode) {
$this->addNode($inode);
}
}
}
/**
* Adds a node to the current list of nodes.
*
* This method uses the appropriate specialized add*() method based
* on the type of the argument.
*
* Overwritten from parent to allow Crawler to be added
*
* @param \DOMNodeList|\DOMNode|array|string|Crawler|null $node A node
*
* @api
*/
public function add(\DOMNodeList|\DOMNode|array|string|Crawler|null $node): void
{
if ($node instanceof Crawler) {
foreach ($node as $childnode) {
$this->addNode($childnode);
}
} else {
parent::add($node);
}
}
/**
* @param \DOMNode $newnode
* @param \DOMNode $referencenode
* @param int $clone
* @return \DOMNode
*/
protected static function importNewnode(\DOMNode $newnode, \DOMNode $referencenode, $clone = 0) {
if ($newnode->ownerDocument !== $referencenode->ownerDocument) {
$referencenode->ownerDocument->preserveWhiteSpace = false;
$newnode = $referencenode->ownerDocument->importNode($newnode, true);
} else {
if ($clone > 0) {
$newnode = $newnode->cloneNode(true);
}
}
return $newnode;
}
// /**
// * Checks whether the first node in the set is disconnected (has no parent node)
// *
// * @return bool
// */
// public function isDisconnected()
// {
// $parent = $this->getNode(0)->parentNode;
// return ($parent == null || $parent->tagName == self::FRAGMENT_ROOT_TAGNAME);
// }
public function __get($name)
{
switch ($name) {
case 'count':
case 'length':
return count($this);
}
throw new \Exception('No such property ' . $name);
}
}