Repository: wasinger/htmlpagedom
Branch: master
Commit: 8be6b95fef7c
Files: 22
Total size: 137.5 KB
Directory structure:
gitextract__rfcem_2/
├── .github/
│ └── workflows/
│ └── tests.yml
├── .gitignore
├── .phpdoc-md
├── .scrutinizer.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── Resources/
│ └── jquerytest.html
├── Tests/
│ ├── HelpersTest.php
│ ├── HtmlPageCrawlerTest.php
│ ├── HtmlPageTest.php
│ ├── phpunit_bootstrap.php
│ └── utf8.html
├── UPGRADE.md
├── composer.json
├── doc/
│ ├── HtmlPage.md
│ ├── HtmlPageCrawler.md
│ └── README.md
├── phpunit.xml.dist
└── src/
├── Helpers.php
├── HtmlPage.php
└── HtmlPageCrawler.php
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/tests.yml
================================================
name: tests
on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:
jobs:
php:
runs-on: ubuntu-latest
strategy:
matrix:
php: [8.1, 8.2, 8.3, 8.4, 8.5]
dependency-version: [prefer-lowest, prefer-stable]
steps:
- name: checkout code
uses: actions/checkout@v4
- name: setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug
- name: install dependencies
run: composer update --${{ matrix.dependency-version }}
- name: run tests
run: php vendor/bin/phpunit
================================================
FILE: .gitignore
================================================
vendor
test.php
composer.lock
composer.phar
================================================
FILE: .phpdoc-md
================================================
<?php
return (object)[
'rootNamespace' => 'Wa72\HtmlPageDom',
'destDirectory' => 'doc',
'format' => 'github',
'classes' => [
'\Wa72\HtmlPageDom\HtmlPage',
'\Wa72\HtmlPageDom\HtmlPageCrawler'
]
];
================================================
FILE: .scrutinizer.yml
================================================
before_commands:
- 'composer install --dev --no-interaction --prefer-source'
tools:
# Code Coverage from Travis
external_code_coverage:
enabled: true
timeout: 300
filter:
excluded_paths:
- 'Tests/*'
- 'vendor/*'
php_code_coverage:
enabled: false
php_code_sniffer:
enabled: true
config:
standard: PSR2
filter:
excluded_paths:
- 'vendor/*'
# PHP Mess Detector (http://phpmd.org).
php_mess_detector:
enabled: true
command: phpmd
config:
rulesets:
- codesize
- unusedcode
- design
filter:
excluded_paths:
- 'vendor/*'
php_pdepend:
enabled: true
excluded_dirs: [vendor, Tests]
php_loc:
enabled: true
excluded_dirs: [vendor, Tests]
php_cpd:
enabled: true
excluded_dirs: [vendor, Tests]
php_analyzer:
enabled: true
filter:
excluded_paths:
- 'Tests/*'
- 'vendor/*'
================================================
FILE: CHANGELOG.md
================================================
3.0.0
=====
2022-04-13
Changed some method signatures (added argument type hints and return types) in HtmlPageCrawler for compatibility with the base Crawler class from Symfony 6. So, this release is only compatible with Symfony 6 and up.
Otherwise there are no changes, so it does not require changes in code using this lib.
2.0.0
=====
2019-10-15
__BC BREAK__ for compatibility with Symfony 4.3 and up
- `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.
- `HtmlPageCrawler::attr()` is now just the parent `Crawler::attr()` and acts as *getter* only.
For setting attributes use `HtmlPageCrawler::setAttribute($name, $value)` 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)
- removed method `HtmlPageCrawler::isDisconnected()`
1.4.2
=====
2019-10-15
- undo deprecation of getInnerHtml()
- deprecate setter use of attr()
- deprecate isDisconnected()
1.4.1
=====
2019-06-28
- Bugfix: setText() should convert special chars. Closes #34.
1.4.0
=====
2019-05-17
Preparation for a smooth migration to 2.x / Symfony 4.3:
- deprecate setter use of html() and text(),
- deprecate getInnerHtml(),
- new methods setText() and getCombinedText()
1.3.2
=====
2019-04-18
- Mark this version as incompatible to Symfony DomCrawler 4.3
1.3
===
2016-10-06
- new method `unwrapInner` (thanks to [@ttk](https://github.com/ttk))
- it's now possible to get the number of nodes in the crawler using the
`$crawler->length` property like in Javascript instead of `count($crawler)`
1.2
===
2015-11-06
- new methods `HtmlPage::minify()` and `HtmlPage::indent()` for compressing or nicely indenting the HTML document. These
functions rely on the package `wa72/html-pretty-min` that is *suggested* in composer.json.
1.1
===
2015-05-20
- `text()` function now returns combined text of all elements in set (as jQuery does; previously only the nodeValue of
the first element was returned) and can act as a setter `text($string)` that sets the nodeValue of all elements to
the specified string
- function `hasClass` now returns true if any of the elements in the Crawler has the specified class (previously,
only the first element was checked).
- new function `makeClone` as equivalent to jQuery's `clone` function ("clone" is not a valid function name in PHP).
As previously, you can alternatively use PHP's clone operator: `$r = $c->makeClone()` is the same as `$r = clone $c`,
but the new function allows chaining.
- new function `removeAttr` aliasing `removeAttribute` for compatibility with jQuery
- `appendTo`, `insertBefore`, `insertAfter`, and `replaceAll` now always return a new Crawler object containing
the aggregate set of all elements appended to the target elements (this is the behavior of jQuery 1.9 and newer).
- `attr` function can now act as setter `attr($name, $value)` which is an alias for `setAttribute($name, $value)`
(previously it accepted only one argument and was a getter equivalent to `getAttribute($name)` only, like it is
in parent DomCrawler)
- `attr($name)` and `getAttribute($name)` now always return `null` if the attribute does not exist (previously, an empty
string was returned when used with Symfony 2.3)
1.0
===
================================================
FILE: LICENSE
================================================
Copyright (c) 2012-2022 Christoph Singer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The software is provided "as is", without warranty of any kind, express or
implied, including but not limited to the warranties of merchantability,
fitness for a particular purpose and noninfringement. In no event shall the
authors or copyright holders be liable for any claim, damages or other
liability, whether in an action of contract, tort or otherwise, arising from,
out of or in connection with the software or the use or other dealings in
the software.
================================================
FILE: README.md
================================================
HtmlPageDom
===========

[](https://packagist.org/packages/wa72/htmlpagedom)
[](https://packagist.org/packages/wa72/htmlpagedom)
`Wa72\HtmlPageDom` is a PHP library for easy manipulation of HTML documents using DOM.
It requires [DomCrawler from Symfony components](https://github.com/symfony/DomCrawler) for traversing
the DOM tree and extends it by adding methods for manipulating the DOM tree of HTML documents.
It's useful when you need to not just extract information from an HTML file (what DomCrawler does) but
also to modify HTML pages. It is usable as a template engine: load your HTML template file, set new
HTML content on certain elements such as the page title, `div#content` or `ul#menu` and print out
the modified page.
`Wa72\HtmlPageDom` consists of two main classes:
- `HtmlPageCrawler` extends `Symfony\Components\DomCrawler` by adding jQuery inspired, HTML specific
DOM *manipulation* functions such as `setInnerHtml($htmltext)`, `before()`, `append()`, `wrap()`, `addClass()` or `css()`.
It's like jQuery for PHP: simply select elements of an HTML page using CSS selectors and change their
attributes and content.
[API doc for HtmlPageCrawler](doc/HtmlPageCrawler.md)
- `HtmlPage` represents one complete HTML document and offers convenience functions like `getTitle()`, `setTitle($title)`,
`setMeta('description', $description)`, `getBody()`. Internally, it uses the `HtmlPageCrawler` class for
filtering and manipulating DOM Elements. Since version 1.2, it offers methods for compressing (`minify()`) and
prettyprinting (`indent()`) the HTML page.
[API doc for HtmlPage](doc/HtmlPage.md)
Requirements and Compatibility
------------------------------
Version 3.x:
- PHP 8.x
- [Symfony\Components\DomCrawler](https://github.com/symfony/DomCrawler) 6.x | 7.x | 8.x
- [Symfony\Components\CssSelector](https://github.com/symfony/CssSelector) 6.x | 7.x | 8.x
Version 2.x:
- PHP ^7.4 | 8.x
- [Symfony\Components\DomCrawler](https://github.com/symfony/DomCrawler) ^4.4 | 5.x
- [Symfony\Components\CssSelector](https://github.com/symfony/CssSelector) ^4.4 | 5.x
There is no difference in our API between versions 2.x and 3.0.x.
The only difference is the compatibility with different versions of Symfony.
Installation
------------
- using [composer](http://getcomposer.org): `composer require wa72/htmlpagedom`
- using other [PSR-4](http://www.php-fig.org/psr/psr-4/) compliant autoloader:
clone this project to where your included libraries are and point your autoloader to look for the
"\Wa72\HtmlPageDom" namespace in the "src" directory of this project
Usage
-----
`HtmlPageCrawler` is a wrapper around DOMNodes. `HtmlPageCrawler` objects can be created using `new` or the static function
`HtmlPageCrawler::create()`, which accepts an HTML string or a DOMNode (or an array of DOMNodes, a DOMNodeList, or even
another `Crawler` object) as arguments.
Afterwards you can select nodes from the added DOM tree by calling `filter()` (equivalent to find() in jQuery) and alter
the selected elements using the following jQuery-like manipulation functions:
- `addClass()`, `hasClass()`, `removeClass()`, `toggleClass()`
- `after()`, `before()`
- `append()`, `appendTo()`
- `makeClone()` (equivalent to `clone()` in jQuery)
- `css()` (alias `getStyle()` / `setStyle()`)
- `html()` (get inner HTML content) and `setInnerHtml($html)`
- `attr()` (alias `getAttribute()` / `setAttribute()`), `removeAttr()`
- `insertAfter()`, `insertBefore()`
- `makeEmpty()` (equivalent to `empty()` in jQuery)
- `prepend()`, `prependTo()`
- `remove()`
- `replaceAll()`, `replaceWith()`
- `text()`, `getCombinedText()` (get text content of all nodes in the Crawler), and `setText($text)`
- `wrap()`, `unwrap()`, `wrapInner()`, `unwrapInner()`, `wrapAll()`
To get the modified DOM as HTML code use `html()` (returns innerHTML of the first node in your crawler object)
or `saveHTML()` (returns combined "outer" HTML code of all elements in the list).
See the full methods documentation in the generated [API doc for HtmlPageCrawler](doc/HtmlPageCrawler.md)
**Example:**
```php
use \Wa72\HtmlPageDom\HtmlPageCrawler;
// create an object from a fragment of HTML code as you would do with jQuery's $() function
$c = HtmlPageCrawler::create('<div id="content"><h1>Title</h1></div>');
// the above is the same as calling:
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1></div>');
// filter for h1 elements and wrap them with an HTML structure
$c->filter('h1')->wrap('<div class="innercontent">');
// return the modified HTML
echo $c->saveHTML();
// or simply:
echo $c; // implicit __toString() calls saveHTML()
// will output: <div id="content"><div class="innercontent"><h1>Title</h1></div></div>
```
**Advanced example: remove the third column from an HTML table**
```php
use \Wa72\HtmlPageDom\HtmlPageCrawler;
$html = <<<END
<table>
<tr>
<td>abc</td>
<td>adsf</td>
<td>to be removed</td>
</tr>
<tr>
<td>abc</td>
<td>adsf</td>
<td>to be removed</td>
</tr>
<tr>
<td>abc</td>
<td>adsf</td>
<td>to be removed</td>
</tr>
</table>
END;
$c = HtmlPageCrawler::create($html);
$tr = $c->filter('table > tr > td')
->reduce(
function ($c, $j) {
if (($j+1) % 3 == 0) {
return true;
}
return false;
}
);
$tr->remove();
echo $c->saveHTML();
```
**Usage examples for the `HtmlPage` class:**
```php
use \Wa72\HtmlPageDom\HtmlPage;
// create a new HtmlPage object with an empty HTML skeleton
$page = new HtmlPage();
// or create a HtmlPage object from an existing page
$page = new HtmlPage(file_get_contents('http://www.heise.de'));
// get or set page title
echo $page->getTitle();
$page->setTitle('New page title');
echo $page->getTitle();
// add HTML content
$page->filter('body')->setInnerHtml('<div id="#content"><h1>This is the headline</h1><p class="text">This is a paragraph</p></div>');
// select elements by css selector
$h1 = $page->filter('#content h1');
$p = $page->filter('p.text');
// change attributes and content of an element
$h1->addClass('headline')->css('margin-top', '10px')->setInnerHtml('This is the <em>new</em> headline');
$p->removeClass('text')->append('<br>There is more than one line in this paragraph');
// add a new paragraph to div#content
$page->filter('#content')->append('<p>This is a new paragraph.</p>');
// 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
================================================
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<title>Testing jquery object identities</title>
</head>
<body>
<h1>Testing jquery object identities</h1>
<p>This page contains javascript code to figure out in which cases jQuery returns references to existing objects
and when it makes copies.</p>
<p>test paragraph 2<span>555</span></p>
<p>test paragraph 3</p>
<script>
(function() {
if ( typeof Object.prototype.uniqueId == "undefined" ) {
var id = 0;
Object.prototype.uniqueId = function() {
if ( typeof this.__uniqueid == "undefined" ) {
this.__uniqueid = ++id;
}
return this.__uniqueid;
};
}
})();
$(document).ready(function(){
var $a = $('<span style="font-weight: bold;"> asdf</span>');
var $b = $('p');
var $h = $('h1');
var $ba, $ha;
$ba = $a.appendTo($b);
$ha = $a.appendTo($h);
console.log('$a: ' + $a.uniqueId());
console.log('span: ' + $a[0].uniqueId());
console.log('$b: ' + $b.uniqueId());
console.log($ba);
console.log('$ba: ' + $ba.uniqueId());
console.log('$ba span 0: ' + $ba[0].uniqueId());
console.log('$ba span 1: ' + $ba[1].uniqueId());
console.log('$ba span 2: ' + $ba[2].uniqueId());
console.log('$ha: ' + $ha.uniqueId());
console.log('$ha span 0: ' + $ha[0].uniqueId());
console.log($b.text());
$b.text('<span>444</span>');
console.log($b.text());
// Test for issue #33 https://github.com/wasinger/htmlpagedom/issues/33
// Works like reporter expects in jquery but not in HmtlPageDom
var $rootNode = $('<div />').appendTo($('body'));
var $p = $('<p />');
var $testNode = $('<span />');
$testNode.text('incorrect text');
$p.append($testNode);
$rootNode.append($p);
// Change test node text after node appended
$testNode.text('correct text');
// Output root or parent node html. Incorrect in HtmlPageDom, Correct in jquery
console.log($rootNode.html());
console.log($p.html());
// Output node html. Correct
console.log($testNode.html());
// Second test: adding node to multiple nodes.
// If $testNode is appended to multple elements it doesn't work in jquery, either:
$rootNode = $('<div />').appendTo($('body'));
$p = $('<p /><p />');
$testNode = $('<span />');
$testNode.text('incorrect text');
$p.append($testNode);
$rootNode.append($p);
// Change test node text after node appended
$testNode.text('correct text');
// Output root or parent node html. Incorrect in jquery and HtmlPageDom
console.log($rootNode.html());
console.log($p.html());
// Output node html. Correct
console.log($testNode.html());
});
</script>
</body>
</html>
================================================
FILE: Tests/HelpersTest.php
================================================
<?php
namespace Wa72\HtmlPageDom\Tests;
use Wa72\HtmlPageDom\Helpers;
use org\bovigo\vfs\vfsStream;
use PHPUnit\Framework\TestCase;
class HelpersTest extends TestCase
{
public function testCssStringToArray()
{
$this->assertEquals([
'font-size' => '15px',
'font-weight' => 'bold',
'font-color' => 'black'
], Helpers::cssStringToArray('invalid_css_string;font-size: 15px;font-weight: bold;font-color: black;'));
}
public function testCssArrayToString()
{
$this->assertEquals('font-size: 15px;font-weight: bold;font-color: black;', Helpers::cssArrayToString([
'font-size' => '15px',
'font-weight' => 'bold',
'font-color' => 'black'
]));
}
}
================================================
FILE: Tests/HtmlPageCrawlerTest.php
================================================
<?php
namespace Wa72\HtmlPageDom\Tests;
use Wa72\HtmlPageDom\HtmlPageCrawler;
use PHPUnit\Framework\TestCase;
class HtmlPageCrawlerTest extends TestCase
{
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::__construct
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::filter
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::getFirstNode
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::nodeName
*/
public function testHtmlPageCrawler()
{
$c = new HtmlPageCrawler;
$c->addHtmlContent('<!doctype html><html><body><div id="content"><h1>Title</h1></div></body></html>');
$title = $c->filter('#content > h1');
$this->assertInstanceOf('\Wa72\HtmlPageDom\HtmlPageCrawler', $title);
$this->assertInstanceOf('\DOMNode', $title->getNode(0));
$this->assertEquals('h1', $title->nodeName());
}
/**
*
*
* @param $string
* @return string
*/
private function _ignoreNewlines($string)
{
return str_replace("\n", '', $string);
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::setInnerHtml
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::prepend
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::makeEmpty
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::setAttribute
*/
public function testManipulationFunctions()
{
$c = new HtmlPageCrawler;
$c->addHtmlContent('<!doctype html><html><body><div id="content"><h1>Title</h1></div></body></html>');
$content = $c->filter('#content');
$content->append('<p>Das ist ein Testabsatz');
$this->assertEquals("<h1>Title</h1><p>Das ist ein Testabsatz</p>", $this->_ignoreNewlines($content->html()));
$content->setInnerHtml('<p>Ein neuer <b>Inhalt</p>');
$this->assertEquals('<p>Ein neuer <b>Inhalt</b></p>', $content->html());
$content->prepend('<h1>Neue Überschrift');
$this->assertEquals('<h1>Neue Überschrift</h1><p>Ein neuer <b>Inhalt</b></p>', $content->html());
$h1 = $content->filter('h1');
$this->assertEquals('Neue Überschrift', $h1->text());
$b = $content->filter('b');
$this->assertEquals('Inhalt', $b->text());
$b2 = $c->filter('#content p b');
$this->assertEquals('Inhalt', $b2->text());
$content->append('<p class="a2">Zweiter Absatz</p>');
$content->append('<p class="a3"><b>Dritter Absatz</b> und noch mehr Text</p>');
$a3 = $content->filter('p.a3');
$this->assertEquals('<b>Dritter Absatz</b> und noch mehr Text', $a3->html());
$a3b = $a3->filter('b');
$this->assertEquals('Dritter Absatz', $a3b->text());
$body = $c->filter('body');
$this->assertEquals('<div id="content"><h1>Neue Überschrift</h1><p>Ein neuer <b>Inhalt</b></p><p class="a2">Zweiter Absatz</p><p class="a3"><b>Dritter Absatz</b> und noch mehr Text</p></div>', $this->_ignoreNewlines($body->html()));
$paragraphs = $c->filter('p');
$this->assertEquals(3, count($paragraphs));
$paragraphs->append('<span class="appended">.</span>');
$this->assertEquals('<p>Ein neuer <b>Inhalt</b><span class="appended">.</span></p><p class="a2">Zweiter Absatz<span class="appended">.</span></p><p class="a3"><b>Dritter Absatz</b> und noch mehr Text<span class="appended">.</span></p>', $c->filter('p')->saveHTML());
$body->makeEmpty();
$this->assertEmpty($body->html());
$body->setAttribute('class', 'mybodyclass');
$this->assertEquals('mybodyclass', $body->attr('class'));
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::append
*/
public function testAppend()
{
// Testing append string to several elements
$c = new HtmlPageCrawler('<p>Paragraph 1</p><p>Paragraph 2</p><p>Paragraph 3</p>');
$c->filter('p')->append('<br>Appended Text');
$this->assertEquals('<p>Paragraph 1<br>Appended Text</p><p>Paragraph 2<br>Appended Text</p><p>Paragraph 3<br>Appended Text</p>', $c->saveHTML());
// Testing append HtmlPageCrawler to several elements
$c = new HtmlPageCrawler('<p>Paragraph 1</p><p>Paragraph 2</p><p>Paragraph 3</p>');
$c->filter('p')->append(new HtmlPageCrawler('<br>Appended Text'));
$this->assertEquals('<p>Paragraph 1<br>Appended Text</p><p>Paragraph 2<br>Appended Text</p><p>Paragraph 3<br>Appended Text</p>', $c->saveHTML());
// Testing append DOMNode to several elements
$c = new HtmlPageCrawler('<p>Paragraph 1</p><p>Paragraph 2</p><p>Paragraph 3</p>');
$app = $c->getDOMDocument()->createElement('span', 'Appended Text');
$c->filter('p')->append($app);
$this->assertEquals('<p>Paragraph 1<span>Appended Text</span></p><p>Paragraph 2<span>Appended Text</span></p><p>Paragraph 3<span>Appended Text</span></p>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><span>Append Self</span></div>');
$c->filter('#content')->append($c->filter('span'));
$this->assertEquals('<div id="content"><span>Append Self</span></div>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::appendTo
*/
public function testAppendTo()
{
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1><em>Big</em></div>');
$c->filter('em')->appendTo($c->filter('h1'));
$this->assertEquals('<div id="content"><h1>Title<em>Big</em></h1></div>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><h1>Self Title</h1></div>');
$c->filter('h1')->appendTo($c->filter('h1'));
$this->assertEquals('<div id="content"><h1>Self Title</h1></div>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::isHtmlDocument
*/
public function testIsHtmlDocument()
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->loadHTML('<!DOCTYPE html><html><body><div id="content"><h1>Title</h1></div></body></html>');
$c = new HtmlPageCrawler($dom);
$this->assertTrue($c->isHtmlDocument());
$t = $c->filter('body');
$this->assertFalse($t->isHtmlDocument());
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1></div>');
$this->assertFalse($c->isHtmlDocument());
$c = new HtmlPageCrawler('<html><body><div id="content"><h1>Title</h1></div></body></html>');
$this->assertTrue($c->isHtmlDocument());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::saveHTML
*/
public function testSaveHTML()
{
$html = "<!DOCTYPE html><html><body><h1>Title</h1><p>Paragraph 1</p><p>Paragraph 2</p></body></html>";
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->loadHTML($html);
$c = new HtmlPageCrawler($dom);
$this->assertEquals($html, $this->_ignoreNewlines($c->saveHTML()));
$ps = $c->filter('p');
$this->assertEquals('<p>Paragraph 1</p><p>Paragraph 2</p>', $ps->saveHTML());
$t = $c->filter('h1');
$this->assertEquals('<h1>Title</h1>', $t->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1></div>');
$this->assertEquals('<div id="content"><h1>Title</h1></div>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::css
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::getStyle
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::setStyle
*/
public function testCss()
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->loadHTML('<!DOCTYPE html><html><body><div id="content"><h1 style=" margin-top:
10px;border-bottom: 1px solid red">Title</h1></div></body></html>');
$c = new HtmlPageCrawler($dom);
$t = $c->filter('h1');
$this->assertEquals('10px', $t->css('margin-top'));
$this->assertEquals('1px solid red', $t->css('border-bottom'));
$t->css('margin-bottom', '20px');
$this->assertEquals('20px', $t->css('margin-bottom'));
$this->assertEquals('10px', $t->getStyle('margin-top'));
$this->assertEquals('<h1 style="margin-top: 10px;border-bottom: 1px solid red;margin-bottom: 20px;">Title</h1>', $t->saveHTML());
$t->setStyle('border-bottom', '');
$this->assertEquals('<h1 style="margin-top: 10px;margin-bottom: 20px;">Title</h1>', $t->saveHTML());
$t->setStyle('padding-top', '0');
$this->assertEquals('<h1 style="margin-top: 10px;margin-bottom: 20px;padding-top: 0;">Title</h1>', $t->saveHTML());
$this->assertEquals('0', $t->getStyle('padding-top'));
$this->assertNull($t->getStyle('border-bottom'));
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::addClass
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::removeClass
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::hasClass
*/
public function testClasses()
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->loadHTML('<!DOCTYPE html><html><body><div id="content"><h1 class="style_class">Title</h1></div></body></html>');
$c = new HtmlPageCrawler($dom);
$t = $c->filter('h1');
$t->addClass('ueberschrift');
$t->addClass('nochneklasse');
$t->addClass('style_class');
$this->assertEquals('<h1 class="style_class ueberschrift nochneklasse">Title</h1>', $t->saveHTML());
$this->assertTrue($t->hasClass('ueberschrift'));
$this->assertTrue($t->hasClass('nochneklasse'));
$this->assertTrue($t->hasClass('style_class'));
$t->removeClass('nochneklasse');
$this->assertTrue($t->hasClass('ueberschrift'));
$this->assertFalse($t->hasClass('nochneklasse'));
$t->addClass('class1 class2');
$this->assertTrue($t->hasClass('class1'));
$this->assertTrue($t->hasClass('class2'));
$c1 = new HtmlPageCrawler('<p class="a"></p><p class="b"></p><p class="c"></p>');
$this->assertTrue($c1->hasClass('b'));
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::addContent
*/
public function testAddContent()
{
$c = new HtmlPageCrawler();
$c->addContent('<html><head></head><body><div id="content"><h1>Title</h1></div></body>');
// The behaviour of Symfony's \Symfony\Component\DomCrawler\Crawler varies based on Symfony version and PHP Version.
// So for this test, we normalize the output.
$html = $c->saveHTML();
// Old parser (PHP < 8.4) or old Symfony: remove added HTML 4.0 DOCTYPE at the beginning of the html
$html = preg_replace('#^\s*<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">\s*#', '', $html);
// Remove XML encoding declaration added by Symfony's UTF-8 handling (varies by Symfony version)
$html = preg_replace('#^\s*<\?xml encoding="UTF-8">\s*#', '', $html);
$html = $this->_ignoreNewlines($html);
$this->assertEquals(
'<html><head></head><body><div id="content"><h1>Title</h1></div></body></html>',
$html
);
$c = new HtmlPageCrawler();
$c->addContent('<div id="content"><h1>Title');
$this->assertEquals('<div id="content"><h1>Title</h1></div>', $c->saveHTML());
$c = new HtmlPageCrawler();
$c->addContent('<p>asdf<p>asdfaf</p>');
$this->assertEquals(2, count($c));
$this->assertEquals('<p>asdf</p><p>asdfaf</p>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::before
*/
public function testBefore()
{
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1></div>');
$c->filter('h1')->before('<p>Text before h1</p>');
$this->assertEquals('<div id="content"><p>Text before h1</p><h1>Title</h1></div>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1></div>');
$c->filter('h1')->before(new HtmlPageCrawler('<p>Text before h1</p><p>and more text before</p>'));
$this->assertEquals('<div id="content"><p>Text before h1</p><p>and more text before</p><h1>Title</h1></div>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><h1>Self Before</h1></div>');
$c->filter('h1')->before($c->filter('h1'));
$this->assertEquals('<div id="content"><h1>Self Before</h1></div>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::insertBefore
*/
public function testInsertBefore()
{
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1><p>Text before h1</p></div>');
$c->filter('p')->insertBefore($c->filter('h1'));
$this->assertEquals('<div id="content"><p>Text before h1</p><h1>Title</h1></div>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><h1>Self Insert Before Title</h1><p>Text after h1</p></div>');
$c->filter('h1')->insertBefore($c->filter('h1'));
$this->assertEquals('<div id="content"><h1>Self Insert Before Title</h1><p>Text after h1</p></div>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::after
*/
public function testAfter()
{
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1></div>');
$c->filter('h1')->after('<p>Text after h1</p>');
$this->assertEquals('<div id="content"><h1>Title</h1><p>Text after h1</p></div>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1><h1>Title2</h1></div>');
$c->filter('h1')->after(new HtmlPageCrawler('<p>Text after h1</p><p>and more text after</p>'));
$this->assertEquals('<div id="content"><h1>Title</h1><p>Text after h1</p><p>and more text after</p><h1>Title2</h1><p>Text after h1</p><p>and more text after</p></div>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><h1>Self After</h1></div>');
$c->filter('h1')->after($c->filter('h1'));
$this->assertEquals('<div id="content"><h1>Self After</h1></div>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::insertAfter
*/
public function testInsertAfter()
{
$c = new HtmlPageCrawler('<div id="content"><p>Text after h1</p><h1>Title</h1></div>');
$c->filter('p')->insertAfter($c->filter('h1'));
$this->assertEquals('<div id="content"><h1>Title</h1><p>Text after h1</p></div>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><p>Text before h1</p><h1>Self Insert After Title</h1></div>');
$c->filter('h1')->insertAfter($c->filter('h1'));
$this->assertEquals('<div id="content"><p>Text before h1</p><h1>Self Insert After Title</h1></div>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::prepend
*/
public function testPrepend()
{
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1></div>');
$c->filter('#content')->prepend('<p>Text before h1</p>');
$this->assertEquals('<div id="content"><p>Text before h1</p><h1>Title</h1></div>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"></div>');
$c->filter('#content')->prepend(new HtmlPageCrawler('<p>Text before h1</p><p>and more text before</p>'));
$this->assertEquals('<div id="content"><p>Text before h1</p><p>and more text before</p></div>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><span>Prepend Self</span></div>');
$c->filter('#content')->prepend($c->filter('span'));
$this->assertEquals('<div id="content"><span>Prepend Self</span></div>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::prependTo
*/
public function testPrependTo()
{
$c = new HtmlPageCrawler('<div id="content"><p>Text before</p></div>');
$c->filter('p')->prependTo('Text');
$this->assertEquals('<div id="content"><p>Text before</p></div>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1></div>');
$c->filter('#content')->prependTo(new HtmlPageCrawler('<p>paragraph</p>'));
$this->assertEquals('<div id="content"><h1>Title</h1></div>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1><em>Big</em></div>');
$c->filter('em')->prependTo($c->filter('h1'));
$this->assertEquals('<div id="content"><h1><em>Big</em>Title</h1></div>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><h1>Self Title</h1></div>');
$c->filter('h1')->prependTo($c->filter('h1'));
$this->assertEquals('<div id="content"><h1>Self Title</h1></div>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::wrap
*/
public function testWrap()
{
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1></div>');
$c->filter('h1')->wrap('<div class="innercontent">');
$this->assertEquals('<div id="content"><div class="innercontent"><h1>Title</h1></div></div>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1></div>');
$c->filter('h1')->wrap('<div class="ic">asdf<div class="a1"><div class="a2"></div></div></div></div>');
$this->assertEquals('<div id="content"><div class="ic">asdf<div class="a1"><div class="a2"><h1>Title</h1></div></div></div></div>', $c->saveHTML());
$c = new HtmlPageCrawler('<div id="content"><h1>Title</h1></div>');
$c->filter('h1')->wrap('<div class="ic">asdf</div><div>jkl</div>'); // wrap has more than 1 root element
$this->assertEquals('<div id="content"><div class="ic">asdf<h1>Title</h1></div></div>', $c->saveHTML()); // only first element is used
// Test for wrapping multiple nodes
$c = new HtmlPageCrawler('<div id="content"><p>p1</p><p>p2</p></div>');
$c->filter('p')->wrap('<div class="p"></div>');
$this->assertEquals('<div id="content"><div class="p"><p>p1</p></div><div class="p"><p>p2</p></div></div>', $c->saveHTML());
$c = new HtmlPageCrawler('plain text node');
$c->wrap('<div class="ic"></div>');
$this->assertEquals('<div class="ic">plain text node</div>', $c->ancestors()->eq(0)->saveHTML());
$c = HtmlPageCrawler::create('<div>');
$m = HtmlPageCrawler::create('message 1')->appendTo($c);
$m->wrap('<p>');
$m = HtmlPageCrawler::create('message 2')->appendTo($c);
$m->wrap('<p>');
$this->assertEquals('<div><p>message 1</p><p>message 2</p></div>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::replaceWith
*/
public function testReplaceWith()
{
$c = HtmlPageCrawler::create('<div id="content"><p>Absatz 1</p><p>Absatz 2</p><p>Absatz 3</p></div>');
$oldparagraphs = $c->filter('p')->replaceWith('<div>newtext 1</div><div>newtext 2</div>');
$this->assertEquals('<div id="content"><div>newtext 1</div><div>newtext 2</div><div>newtext 1</div><div>newtext 2</div><div>newtext 1</div><div>newtext 2</div></div>', $c->saveHTML());
$this->assertEquals('<p>Absatz 1</p><p>Absatz 2</p><p>Absatz 3</p>', $oldparagraphs->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::replaceAll
*/
public function testReplaceAll()
{
$c = HtmlPageCrawler::create('<div id="content"><p>Absatz 1</p><p>Absatz 2</p><p>Absatz 3</p></div>');
$new = HtmlPageCrawler::create('<div>newtext 1</div><div>newtext 2</div>');
$new->replaceAll($c->filter('p'));
$this->assertEquals('<div id="content"><div>newtext 1</div><div>newtext 2</div><div>newtext 1</div><div>newtext 2</div><div>newtext 1</div><div>newtext 2</div></div>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::wrapAll
*/
public function testWrapAll()
{
$c = HtmlPageCrawler::create('<div id="content"><div>Before</div><p>Absatz 1</p><div>Inner</div><p>Absatz 2</p><p>Absatz 3</p><div>After</div></div>');
$c->filter('p')->wrapAll('<div class="a">');
$this->assertEquals('<div id="content"><div>Before</div><div class="a"><p>Absatz 1</p><p>Absatz 2</p><p>Absatz 3</p></div><div>Inner</div><div>After</div></div>', $c->saveHTML());
// Test for wrapping with elements that have children
$c = HtmlPageCrawler::create('<div id="content"><p>Absatz 1</p><p>Absatz 2</p><p>Absatz 3</p></div>');
$c->filter('p')->wrapAll('<article><section><div class="a"></div></section></article>');
$this->assertEquals('<div id="content"><article><section><div class="a"><p>Absatz 1</p><p>Absatz 2</p><p>Absatz 3</p></div></section></article></div>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::wrapInner
*/
public function testWrapInner()
{
$c = HtmlPageCrawler::create('<div id="content"><p>Absatz 1</p><p>Absatz 2</p><p>Absatz 3</p></div>');
$c->wrapInner('<div class="a">');
$this->assertEquals('<div id="content"><div class="a"><p>Absatz 1</p><p>Absatz 2</p><p>Absatz 3</p></div></div>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::unwrap
*/
public function testUnwrap()
{
$c = HtmlPageCrawler::create('<div id="content"><div>Before</div><div class="a"><p>Absatz 1</p></div><div>After</div></div>');
$p = $c->filter('p');
$p->unwrap();
$this->assertEquals('<div id="content"><div>Before</div><p>Absatz 1</p><div>After</div></div>', $c->saveHTML());
}
public function testUnwrapInnerOnDOMElementException()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('DOMElement does not have a parent DOMElement node.');
$c = HtmlPageCrawler::create('<div id="content"></div>');
$p = $c->filter('div#content');
$p->unwrapInner();
$p->unwrapInner();
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::unwrapInner
*/
public function testUnwrapInner()
{
$c = HtmlPageCrawler::create('<div id="content"><div>Before</div><div class="a"><p>Absatz 1</p></div><div>After</div></div>');
$p = $c->filter('div.a');
$p->unwrapInner();
$this->assertEquals('<div id="content"><div>Before</div><p>Absatz 1</p><div>After</div></div>', $c->saveHTML());
}
/**
* @covers Wa72\HtmlPageDom\HtmlPageCrawler::toggleClass
*/
public function testToggleClass()
{
$c = HtmlPageCrawler::create('<div id="1" class="a c"><div id="2" class="b c"></div></div>');
$c->filter('div')->toggleClass('a d')->toggleClass('b');
$this->assertEquals('<div id="1" class="c d b"><div id="2" class="c a d"></div></div>', $c->saveHTML());
}
public function testRemove()
{
// remove every third td in tbody
$html = <<<END
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
</tr>
</thead>
<tbody>
<tr class="r1">
<td class="c11">16.12.2013</td>
<td class="c12">asdf asdf</td>
<td class="c13"> </td>
</tr>
<tr class="r2">
<td class="c21">02.12.2013 16:30</td>
<td class="c22">asdf asdf</td>
<td class="c23"> </td>
</tr>
<tr class="r3">
<td class="c31">25.11.2013 16:30</td>
<td class="c32">asdf asdf</td>
<td class="c33"> </td>
</tr>
<tr class="r4">
<td class="c41">18.11.2013 16:30</td>
<td class="c42">asdf asdf</td>
<td class="c43"> </td>
</tr>
<tr class="r5">
<td class="c51">24.10.2013 16:30</td>
<td class="c52">asdf asdf</td>
<td class="c53"> </td>
</tr>
<tr class="r6">
<td class="c61">10.10.2013 16:30</td>
<td class="c62">asdf asdf</td>
<td class="c63"> </td>
</tr>
</table>
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
<p style="margin: 0cm 0cm 0pt;"><span>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.</span></p><p style="margin: 0cm 0cm 0pt;"><span>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.</span></p>
END;
$this->assertEquals($expected, $c->filter('p')->saveHTML());
}
public function testAttr()
{
$c = HtmlPageCrawler::create('<div>');
$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('<h1>Title</h1>');
$this->assertInstanceOf('Wa72\HtmlPageDom\HtmlPageCrawler', $html->setInnerHtml('<h2>Title</h2>'));
$this->assertEquals('<h2>Title</h2>', $html->html());
// getInnerHtml is just an alias for html() and should provide the same result
$this->assertEquals('<h2>Title</h2>', $html->getInnerHtml());
}
public function testToString()
{
$html = HtmlPageCrawler::create('<h2>Title</h2>');
$this->assertEquals('<h2>Title</h2>', (string) $html);
}
public function testGetDOMDocument()
{
$html = HtmlPageCrawler::create('<h2>Title</h2>');
$this->assertInstanceOf('\DOMDocument', $html->getDOMDocument());
}
public function testAddOnCrawlerInstance()
{
$html = HtmlPageCrawler::create('<h1>Title</h1>');
$html->add($html);
$this->assertEquals('<h1>Title</h1>', (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('<h1>Headline</h1>');
$c2 = HtmlPageCrawler::create('<p>1</p><p>2</p><p>3</p>');
$c3 = HtmlPageCrawler::create('<span>asdf</span>');
$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('<div>abc</div>');
$r = HtmlPageCrawler::create('<div>def</div>');
$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('<div><p class="x">asdf</p></div>');
$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('<div><p class="x">asdf</p><p class="">asdf</p></div>', $c->saveHTML());
}
public function testGetCombinedText()
{
$c = HtmlPageCrawler::create('<p>abc</p><p>def</p>');
$this->assertEquals('abcdef', $c->getCombinedText());
$c->setText('jklo');
$this->assertEquals('jklojklo', $c->getCombinedText());
}
public function testSetText()
{
$c = HtmlPageCrawler::create('<div>"</div>');
$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('<p>abc</p><p>def</p>');
$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
================================================
<?php
namespace Wa72\HtmlPageDom\Tests;
use Wa72\HtmlPageDom\HtmlPage;
use org\bovigo\vfs\vfsStream;
use PHPUnit\Framework\TestCase;
class HtmlPageTest extends TestCase
{
public function setUp(): void
{
$this->root = vfsStream::setup('root');
}
public function testHtmlPage()
{
$hp = new HtmlPage;
$this->assertEquals("<!DOCTYPE html>\n<html><head><title></title></head><body></body></html>\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 = '<div id="content">Testcontent1</div>';
$body = $hp->filter('body');
$body->setInnerHtml($bodycontent);
$this->assertEquals($bodycontent, $body->html());
$this->assertEquals($bodycontent, $hp->filter('body')->html());
$content = "<h1>Überschrift</h1>\n<p>bla bla <br><b>fett</b></p>";
$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("<!DOCTYPE html>\n<html><head><title></title></head><body></body></html>\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
<!DOCTYPE html>
<html>
<head>
<title></title>
<script>
// this will be awesome
alert('Hello world');
</script>
</head>
<body>
</body>
</html>
END;
$hp = new HtmlPage($html);
$hp->getBody()->append('<h1>Script Test</h1>');
$newhtml = $hp->save();
$expected =<<<END
<!DOCTYPE html>
<html>
<head>
<title></title>
<script>
// this will be awesome
alert('Hello world');
</script>
</head>
<body>
<h1>Script Test</h1></body>
</html>
END;
$this->assertEquals($expected, $newhtml);
}
public function testMinify()
{
$html =<<<END
<!DOCTYPE html>
<html>
<head>
<title></title>
<script>
// this will be awesome
alert('Hello world');
</script>
</head>
<body>
<h1>TEST</h1>
<p class="">
asdf jksdlf ajsfk
<b>jasdf
jaksfd asdf</b>
<a>jasdf jaks</a>
</p>
</body>
</html>
END;
$hp = new HtmlPage($html);
$expected = <<<END
<!DOCTYPE html>
<html><head><title></title><script>alert('Hello world');</script></head><body><h1>TEST</h1><p>asdf jksdlf ajsfk <b>jasdf jaksfd asdf</b> <a>jasdf jaks</a></p></body></html>
END;
$this->assertEquals($expected, $hp->minify()->save());
}
public function testIndent()
{
$html =<<<END
<!DOCTYPE html>
<html>
<head>
<title></title>
<script>
// this will be awesome
alert('Hello world');
</script>
</head>
<body>
<h1>TEST</h1>
<p>
asdf jksdlf ajsfk
<b>jasdf
jaksfd asdf</b>
<a>jasdf jaks</a>
</p>
</body>
</html>
END;
$hp = new HtmlPage($html);
$expected = <<<END
<!DOCTYPE html>
<html>
<head>
<title></title>
<script>
// this will be awesome
alert('Hello world');
</script>
</head>
<body>
<h1>TEST</h1>
<p>asdf jksdlf ajsfk <b>jasdf jaksfd asdf</b> <a>jasdf jaks</a></p>
</body>
</html>
END;
$this->assertEquals($expected, $hp->indent()->save());
}
public function testGetCrawler()
{
$html = <<<END
<!DOCTYPE html>
<html>
<head>
<title></title>
<script>
// this will be awesome
alert('Hello world');
</script>
</head>
<body>
<h1>TEST</h1>
<p class="">
asdf jksdlf ajsfk
<b>jasdf
jaksfd asdf</b>
<a>jasdf jaks</a>
</p>
</body>
</html>
END;
$hp = new HtmlPage($html);
$this->assertEquals('<h1>TEST</h1>', $hp->getCrawler()->filter('h1')->saveHtml());
}
public function testGetDOMDocument()
{
$html = <<<END
<!DOCTYPE html>
<html>
<head>
<title></title>
<script>
// this will be awesome
alert('Hello world');
</script>
</head>
<body>
<h1>TEST</h1>
<p class="">
asdf jksdlf ajsfk
<b>jasdf
jaksfd asdf</b>
<a>jasdf jaks</a>
</p>
</body>
</html>
END;
$hp = new HtmlPage($html);
$this->assertInstanceOf('\DOMDocument', $hp->getDOMDocument());
}
public function testSetTitleOnNoTitleElement()
{
$html = <<<END
<!DOCTYPE html>
<html>
<head>
<script>
// this will be awesome
alert('Hello world');
</script>
</head>
<body>
<h1>TEST</h1>
<p class="">
asdf jksdlf ajsfk
<b>jasdf
jaksfd asdf</b>
<a>jasdf jaks</a>
</p>
</body>
</html>
END;
$hp = new HtmlPage($html);
$hp->setTitle('TEST');
$this->assertEquals('TEST', $hp->getTitle());
}
public function testGetTitleShouldReturnNull()
{
$html = <<<END
<!DOCTYPE html>
<html>
<head>
<script>
// this will be awesome
alert('Hello world');
</script>
</head>
<body>
<h1>TEST</h1>
<p class="">
asdf jksdlf ajsfk
<b>jasdf
jaksfd asdf</b>
<a>jasdf jaks</a>
</p>
</body>
</html>
END;
$hp = new HtmlPage($html);
$this->assertNull($hp->getTitle());
}
public function testGetBaseHrefShouldReturnNull()
{
$hp = new HtmlPage('<!DOCTYPE html><html><head><title>TEST</title></head><body>Hello</body></html>');
$this->assertNull($hp->getBaseHref());
}
public function testGetHeadNodeShouldAddTheHeadTag()
{
$hp = new HtmlPage('<!DOCTYPE html><html><body>Hello</body></html>');
$this->assertInstanceOf('\DOMElement', $hp->getHeadNode());
$this->assertEquals('<head></head>', (string) $hp->getHead());
}
public function testGetBodyNodeShouldAddTheBodyTag()
{
$hp = new HtmlPage('<!DOCTYPE html><html></html>');
$this->assertInstanceOf('\DOMElement', $hp->getBodyNode());
$this->assertEquals('<body></body>', (string) $hp->getBody());
}
public function testTrimNewlines()
{
$html = <<<END
<!DOCTYPE html>
<html>
<head>
<title>TEST</title>
</head>
</html>
END;
$this->assertEquals('<!DOCTYPE html> <html> <head> <title>TEST</title> </head> </html>', (string) HtmlPage::trimNewlines($html));
}
public function testSaveOnFileName()
{
$hp = new HtmlPage('<!DOCTYPE html><html><head><title>TEST</title></head></html>');
$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 = <<<END
<!DOCTYPE html>
<html lang="de">
<head>
<title>test</title>
</head>
<body>
<div>
<script>
var html = '<b>Status</b><div>' + it_status_text + '</div>';
</script>
</div>
</body>
</html>
END;
$hp = new HtmlPage($html);
$this->assertEquals($html . "\n", $hp->save());
}
}
================================================
FILE: Tests/phpunit_bootstrap.php
================================================
<?php
// if we are checked out as a stand-alone project
$loader = __DIR__ . '/../vendor/autoload.php';
// if we are within the vendor directory of another project
if (file_exists(__DIR__ . '/../../../../vendor/autoload.php')) {
$loader = __DIR__ . '/../../../../vendor/autoload.php';
}
if (!$loader = @include($loader)) {
echo <<<EOM
You must set up the project dependencies by running the following commands:
curl -s http://getcomposer.org/installer | php
php composer.phar install
EOM;
exit(1);
}
================================================
FILE: Tests/utf8.html
================================================
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
</head>
<body>
<p style="margin: 0cm 0cm 0pt;"><span>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.</span></p>
<p style="margin: 0cm 0cm 0pt;"><span>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.</span></p>
</body>
</html>
================================================
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`
<hr />
### HtmlPage::__construct
**Description**
```php
__construct (void)
```
**Parameters**
`This function has no parameters.`
**Return Values**
`void`
<hr />
### HtmlPage::__toString
**Description**
```php
__toString (void)
```
**Parameters**
`This function has no parameters.`
**Return Values**
`void`
<hr />
### HtmlPage::filter
**Description**
```php
public filter (string $selector)
```
Filter nodes by using a CSS selector
**Parameters**
* `(string) $selector`
: CSS selector
**Return Values**
`\HtmlPageCrawler`
<hr />
### HtmlPage::filterXPath
**Description**
```php
public filterXPath (string $xpath)
```
Filter nodes by XPath expression
**Parameters**
* `(string) $xpath`
: XPath expression
**Return Values**
`\HtmlPageCrawler`
<hr />
### 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`
<hr />
### 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`
<hr />
### HtmlPage::getBodyNode
**Description**
```php
public getBodyNode (void)
```
Get the document's body as DOMElement
**Parameters**
`This function has no parameters.`
**Return Values**
`\DOMElement`
<hr />
### 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`
<hr />
### HtmlPage::getDOMDocument
**Description**
```php
public getDOMDocument (void)
```
Get a DOMDocument object for the HTML document
**Parameters**
`This function has no parameters.`
**Return Values**
`\DOMDocument`
<hr />
### 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`
<hr />
### 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`
<hr />
### HtmlPage::getHeadNode
**Description**
```php
public getHeadNode (void)
```
Get the document's HEAD section as DOMElement
**Parameters**
`This function has no parameters.`
**Return Values**
`\DOMElement`
<hr />
### 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`
<hr />
### 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`
<hr />
### 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`
<hr />
### 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`
<hr />
### HtmlPage::removeMeta
**Description**
```php
public removeMeta (string $name)
```
Remove all meta tags with the specified name attribute
**Parameters**
* `(string) $name`
**Return Values**
`void`
<hr />
### 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`
<hr />
### 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`
<hr />
### 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`
<hr />
### HtmlPage::setMeta
**Description**
```php
public setMeta ( $name, $content)
```
Set a META tag with specified 'name' and 'content' attributes
**Parameters**
* `() $name`
* `() $content`
**Return Values**
`void`
<hr />
### HtmlPage::setTitle
**Description**
```php
public setTitle (string $title)
```
Sets the page title of the HTML document
**Parameters**
* `(string) $title`
**Return Values**
`void`
<hr />
### 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`
<hr />
================================================
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`
<hr />
### HtmlPageCrawler::__get
**Description**
```php
__get (void)
```
**Parameters**
`This function has no parameters.`
**Return Values**
`void`
<hr />
### HtmlPageCrawler::__toString
**Description**
```php
__toString (void)
```
**Parameters**
`This function has no parameters.`
**Return Values**
`void`
<hr />
### 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
<hr />
### HtmlPageCrawler::addHtmlFragment
**Description**
```php
addHtmlFragment (void)
```
**Parameters**
`This function has no parameters.`
**Return Values**
`void`
<hr />
### 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
<hr />
### 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
<hr />
### 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
<hr />
### 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
<hr />
### 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`
<hr />
### 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
<hr />
### 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
<hr />
### 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`
<hr />
### HtmlPageCrawler::getDOMDocument
**Description**
```php
public getDOMDocument (void)
```
get ownerDocument of the first element
**Parameters**
`This function has no parameters.`
**Return Values**
`\DOMDocument|null`
<hr />
### 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`
<hr />
### 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
<hr />
### 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`
<hr />
### 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
<hr />
### 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
<hr />
### 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`
<hr />
### 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`
<hr />
### 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
<hr />
### 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
<hr />
### 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
<hr />
### 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`
<hr />
### 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`
<hr />
### 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`
<hr />
### 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
<hr />
### 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
<hr />
### 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
<hr />
### 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)
<hr />
### 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
<hr />
### 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
<hr />
### 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
<hr />
### HtmlPageCrawler::setText
**Description**
```php
public setText (string $text)
```
Set the text contents of the matched elements.
**Parameters**
* `(string) $text`
**Return Values**
`\HtmlPageCrawler`
<hr />
### 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
<hr />
### 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
<hr />
### 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
<hr />
### 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: <div><div></div></div>
Does not work: <div></div><div></div>
**Parameters**
* `(string|\HtmlPageCrawler|\DOMNode) $wrappingElement`
**Return Values**
`\HtmlPageCrawler`
> $this for chaining
<hr />
### 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`
<hr />
### 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
<hr />
================================================
FILE: doc/README.md
================================================
# Wa72\HtmlPageDom
* [HtmlPage](HtmlPage.md)
* [HtmlPageCrawler](HtmlPageCrawler.md)
================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" bootstrap="./Tests/phpunit_bootstrap.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage>
<include>
<directory suffix=".php">./src/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="HtmlPageDom Test Suite">
<directory suffix="Test.php">./Tests/</directory>
</testsuite>
</testsuites>
</phpunit>
================================================
FILE: src/Helpers.php
================================================
<?php
namespace Wa72\HtmlPageDom;
/**
* Static helper functions for HtmlPageDom
*
* @package Wa72\HtmlPageDom
*/
class Helpers {
/**
* 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)
{
$string = str_replace("\n", ' ', $string);
$string = str_replace("\r", ' ', $string);
$string = preg_replace('/\s+/', ' ', $string);
return trim($string);
}
/**
* Convert CSS string to array
*
* @param string $css list of CSS properties separated by ;
* @return array name=>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><body>' . $html . '</body></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 <fabien@symfony.com>
* 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 <fabien@symfony.com>
* 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
================================================
<?php
namespace Wa72\HtmlPageDom;
use Symfony\Component\CssSelector\CssSelector;
use Wa72\HtmlPrettymin\PrettyMin;
/**
* 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.
*
* @author Christoph Singer
* @license MIT
*/
class HtmlPage
{
/**
*
* @var \DOMDocument
*/
protected $dom;
/**
* @var string
*/
protected $charset;
/**
* @var string
*/
protected $url;
/**
*
* @var HtmlPageCrawler
*/
protected $crawler;
public function __construct($content = '', $url = '', $charset = 'UTF-8')
{
$this->charset = $charset;
$this->url = $url;
if ($content == '') {
$content = '<!DOCTYPE html><html><head><title></title></head><body></body></html>';
}
$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
================================================
<?php
namespace Wa72\HtmlPageDom;
use Symfony\Component\DomCrawler\Crawler;
/**
* 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()
*
* @author Christoph Singer
* @license MIT
*
*/
class HtmlPageCrawler extends Crawler
{
/**
* the (internal) root element name used when importing html fragments
* */
const FRAGMENT_ROOT_TAGNAME = '_root';
/**
* 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.
*
* @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList|array $content
* @return HtmlPageCrawler
* @api
*/
public static function create($content)
{
if ($content instanceof HtmlPageCrawler) {
return $content;
} else {
return new HtmlPageCrawler($content);
}
}
/**
* Adds the specified class(es) to each element in the set of matched elements.
*
* @param string $name One or more space-separated classes to be added to the class attribute of each matched element.
* @return HtmlPageCrawler $this for chaining
* @api
*/
public function addClass($name)
{
foreach ($this as $node) {
if ($node instanceof \DOMElement) {
/** @var \DOMElement $node */
$classes = preg_split('/\s+/s', $node->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: <div><div></div></div>
* Does not work: <div></div><div></div>
*
* @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('/<html\b[^>]*>/i', $content)) {
// string contains no <html> 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);
}
}
gitextract__rfcem_2/
├── .github/
│ └── workflows/
│ └── tests.yml
├── .gitignore
├── .phpdoc-md
├── .scrutinizer.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── Resources/
│ └── jquerytest.html
├── Tests/
│ ├── HelpersTest.php
│ ├── HtmlPageCrawlerTest.php
│ ├── HtmlPageTest.php
│ ├── phpunit_bootstrap.php
│ └── utf8.html
├── UPGRADE.md
├── composer.json
├── doc/
│ ├── HtmlPage.md
│ ├── HtmlPageCrawler.md
│ └── README.md
├── phpunit.xml.dist
└── src/
├── Helpers.php
├── HtmlPage.php
└── HtmlPageCrawler.php
SYMBOL INDEX (140 symbols across 6 files)
FILE: Tests/HelpersTest.php
class HelpersTest (line 8) | class HelpersTest extends TestCase
method testCssStringToArray (line 10) | public function testCssStringToArray()
method testCssArrayToString (line 19) | public function testCssArrayToString()
FILE: Tests/HtmlPageCrawlerTest.php
class HtmlPageCrawlerTest (line 7) | class HtmlPageCrawlerTest extends TestCase
method testHtmlPageCrawler (line 15) | public function testHtmlPageCrawler()
method _ignoreNewlines (line 32) | private function _ignoreNewlines($string)
method testManipulationFunctions (line 43) | public function testManipulationFunctions()
method testAppend (line 95) | public function testAppend()
method testAppendTo (line 121) | public function testAppendTo()
method testIsHtmlDocument (line 135) | public function testIsHtmlDocument()
method testSaveHTML (line 156) | public function testSaveHTML()
method testCss (line 177) | public function testCss()
method testClasses (line 203) | public function testClasses()
method testAddContent (line 230) | public function testAddContent()
method testBefore (line 265) | public function testBefore()
method testInsertBefore (line 283) | public function testInsertBefore()
method testAfter (line 297) | public function testAfter()
method testInsertAfter (line 315) | public function testInsertAfter()
method testPrepend (line 329) | public function testPrepend()
method testPrependTo (line 347) | public function testPrependTo()
method testWrap (line 369) | public function testWrap()
method testReplaceWith (line 403) | public function testReplaceWith()
method testReplaceAll (line 414) | public function testReplaceAll()
method testWrapAll (line 425) | public function testWrapAll()
method testWrapInner (line 440) | public function testWrapInner()
method testUnwrap (line 450) | public function testUnwrap()
method testUnwrapInnerOnDOMElementException (line 458) | public function testUnwrapInnerOnDOMElementException()
method testUnwrapInner (line 472) | public function testUnwrapInner()
method testToggleClass (line 483) | public function testToggleClass()
method testRemove (line 490) | public function testRemove()
method testUTF8Characters (line 551) | public function testUTF8Characters()
method testAttr (line 563) | public function testAttr()
method testAttrOnInvalidNodeList (line 581) | public function testAttrOnInvalidNodeList()
method testSetInnerHtml (line 588) | public function testSetInnerHtml()
method testToString (line 597) | public function testToString()
method testGetDOMDocument (line 603) | public function testGetDOMDocument()
method testAddOnCrawlerInstance (line 609) | public function testAddOnCrawlerInstance()
method testReturnValues (line 616) | public function testReturnValues()
method testDisconnectedNodes (line 652) | public function testDisconnectedNodes()
method testClone (line 674) | public function testClone()
method testGetCombinedText (line 689) | public function testGetCombinedText()
method testSetText (line 697) | public function testSetText()
method testMagicGet (line 705) | public function testMagicGet()
FILE: Tests/HtmlPageTest.php
class HtmlPageTest (line 8) | class HtmlPageTest extends TestCase
method setUp (line 10) | public function setUp(): void
method testHtmlPage (line 15) | public function testHtmlPage()
method testClone (line 52) | public function testClone()
method testScript (line 70) | public function testScript()
method testMinify (line 110) | public function testMinify()
method testIndent (line 145) | public function testIndent()
method testGetCrawler (line 192) | public function testGetCrawler()
method testGetDOMDocument (line 221) | public function testGetDOMDocument()
method testSetTitleOnNoTitleElement (line 250) | public function testSetTitleOnNoTitleElement()
method testGetTitleShouldReturnNull (line 279) | public function testGetTitleShouldReturnNull()
method testGetBaseHrefShouldReturnNull (line 307) | public function testGetBaseHrefShouldReturnNull()
method testGetHeadNodeShouldAddTheHeadTag (line 313) | public function testGetHeadNodeShouldAddTheHeadTag()
method testGetBodyNodeShouldAddTheBodyTag (line 320) | public function testGetBodyNodeShouldAddTheBodyTag()
method testTrimNewlines (line 327) | public function testTrimNewlines()
method testSaveOnFileName (line 341) | public function testSaveOnFileName()
method testEmbeddedScriptWithHtml (line 348) | public function testEmbeddedScriptWithHtml()
FILE: src/Helpers.php
class Helpers (line 9) | class Helpers {
method trimNewlines (line 18) | public static function trimNewlines($string)
method cssStringToArray (line 32) | public static function cssStringToArray($css)
method cssArrayToString (line 58) | public static function cssArrayToString($array)
method getBodyNodeFromHtmlFragment (line 75) | public static function getBodyNodeFromHtmlFragment($html, $charset = '...
method loadHtml (line 83) | public static function loadHtml(string $html, $charset = 'UTF-8'): \DO...
method parseXhtml (line 92) | private static function parseXhtml(string $htmlContent, string $charse...
method convertToHtmlEntities (line 119) | private static function convertToHtmlEntities(string $htmlContent, str...
FILE: src/HtmlPage.php
class HtmlPage (line 18) | class HtmlPage
method __construct (line 42) | public function __construct($content = '', $url = '', $charset = 'UTF-8')
method getCrawler (line 58) | public function getCrawler()
method getDOMDocument (line 68) | public function getDOMDocument()
method setTitle (line 78) | public function setTitle($title)
method getTitle (line 93) | public function getTitle()
method setMeta (line 111) | public function setMeta($name, $content)
method removeMeta (line 128) | public function removeMeta($name)
method getMeta (line 140) | public function getMeta($name)
method setBaseHref (line 155) | public function setBaseHref($url)
method getBaseHref (line 170) | public function getBaseHref()
method setHtmlById (line 186) | public function setHtmlById($elementId, $html)
method getHeadNode (line 196) | public function getHeadNode()
method getBodyNode (line 211) | public function getBodyNode()
method getHead (line 226) | public function getHead()
method getBody (line 236) | public function getBody()
method __toString (line 241) | public function __toString()
method save (line 252) | public function save($filename = '')
method getElementById (line 268) | public function getElementById($id)
method filter (line 279) | public function filter($selector)
method filterXPath (line 291) | public function filterXPath($xpath)
method trimNewlines (line 304) | public static function trimNewlines($string)
method __clone (line 309) | public function __clone()
method minify (line 322) | public function minify(array $options = array())
method indent (line 339) | public function indent(array $options = array())
FILE: src/HtmlPageCrawler.php
class HtmlPageCrawler (line 15) | class HtmlPageCrawler extends Crawler
method create (line 31) | public static function create($content)
method addClass (line 47) | public function addClass($name)
method after (line 76) | public function after($content)
method append (line 106) | public function append($content)
method appendTo (line 131) | public function appendTo($element)
method setAttribute (line 157) | public function setAttribute($name, $value)
method getAttribute (line 176) | public function getAttribute($name)
method before (line 188) | public function before($content)
method makeClone (line 216) | public function makeClone()
method __clone (line 221) | public function __clone()
method css (line 245) | public function css($key, $value = null)
method getStyle (line 260) | public function getStyle($key)
method setStyle (line 273) | public function setStyle($key, $value)
method makeEmpty (line 297) | public function makeEmpty()
method hasClass (line 312) | public function hasClass($name)
method setInnerHtml (line 332) | public function setInnerHtml($content)
method getInnerHtml (line 353) | public function getInnerHtml()
method insertAfter (line 365) | public function insertAfter($element)
method insertBefore (line 393) | public function insertBefore($element)
method prepend (line 418) | public function prepend($content)
method prependTo (line 448) | public function prependTo($element)
method remove (line 479) | public function remove()
method removeAttr (line 501) | public function removeAttr($name)
method removeAttribute (line 512) | public function removeAttribute($name)
method removeClass (line 532) | public function removeClass($name)
method replaceAll (line 557) | public function replaceAll($element)
method replaceWith (line 586) | public function replaceWith($content)
method getCombinedText (line 618) | public function getCombinedText()
method setText (line 635) | public function setText($text)
method toggleClass (line 652) | public function toggleClass($classname)
method unwrap (line 675) | public function unwrap()
method unwrapInner (line 692) | public function unwrapInner()
method wrap (line 721) | public function wrap($wrappingElement)
method wrapAll (line 767) | public function wrapAll($content)
method wrapInner (line 813) | public function wrapInner($content)
method saveHTML (line 831) | public function saveHTML()
method __toString (line 846) | public function __toString()
method isHtmlDocument (line 857) | public function isHtmlDocument()
method getDOMDocument (line 876) | public function getDOMDocument()
method filter (line 894) | public function filter(string $selector): static
method filterXPath (line 908) | public function filterXPath($xpath): static
method addContent (line 924) | public function addContent($content, $type = null): void
method addHtmlFragment (line 937) | public function addHtmlFragment($content, $charset = 'UTF-8')
method add (line 963) | public function add(\DOMNodeList|\DOMNode|array|string|Crawler|null $n...
method importNewnode (line 980) | protected static function importNewnode(\DOMNode $newnode, \DOMNode $r...
method __get (line 1003) | public function __get($name)
Condensed preview — 22 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (148K chars).
[
{
"path": ".github/workflows/tests.yml",
"chars": 670,
"preview": "name: tests\n\non:\n push:\n branches:\n - master\n pull_request:\n branches:\n - master\n workflow_dispatch:\n"
},
{
"path": ".gitignore",
"chars": 44,
"preview": "vendor\ntest.php\ncomposer.lock\ncomposer.phar\n"
},
{
"path": ".phpdoc-md",
"chars": 232,
"preview": "<?php\nreturn (object)[\n 'rootNamespace' => 'Wa72\\HtmlPageDom',\n 'destDirectory' => 'doc',\n 'format' => 'github'"
},
{
"path": ".scrutinizer.yml",
"chars": 1225,
"preview": "before_commands:\n - 'composer install --dev --no-interaction --prefer-source'\n\ntools:\n # Code Coverage from Travis"
},
{
"path": "CHANGELOG.md",
"chars": 3759,
"preview": "3.0.0\n=====\n\n2022-04-13\n\nChanged some method signatures (added argument type hints and return types) in HtmlPageCrawler "
},
{
"path": "LICENSE",
"chars": 1065,
"preview": "Copyright (c) 2012-2022 Christoph Singer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "README.md",
"chars": 9241,
"preview": "HtmlPageDom\n===========\n\n \n* [HtmlPageCrawler](HtmlPageCrawler.md) \n"
},
{
"path": "phpunit.xml.dist",
"chars": 500,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" colors=\"true\" boot"
},
{
"path": "src/Helpers.php",
"chars": 4497,
"preview": "<?php\nnamespace Wa72\\HtmlPageDom;\n\n/**\n * Static helper functions for HtmlPageDom\n *\n * @package Wa72\\HtmlPageDom\n */\ncl"
},
{
"path": "src/HtmlPage.php",
"chars": 8962,
"preview": "<?php\nnamespace Wa72\\HtmlPageDom;\n\nuse Symfony\\Component\\CssSelector\\CssSelector;\nuse Wa72\\HtmlPrettymin\\PrettyMin;\n\n/**"
},
{
"path": "src/HtmlPageCrawler.php",
"chars": 31768,
"preview": "<?php\nnamespace Wa72\\HtmlPageDom;\n\nuse Symfony\\Component\\DomCrawler\\Crawler;\n\n/**\n * Extends \\Symfony\\Component\\DomCrawl"
}
]
About this extraction
This page contains the full source code of the wasinger/htmlpagedom GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 22 files (137.5 KB), approximately 37.2k tokens, and a symbol index with 140 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.