[
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: tests\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n  workflow_dispatch:\n\njobs:\n  php:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        php: [8.1, 8.2, 8.3, 8.4, 8.5]\n        dependency-version: [prefer-lowest, prefer-stable]\n\n    steps:\n      - name: checkout code\n        uses: actions/checkout@v4\n\n      - name: setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: ${{ matrix.php }}\n          coverage: xdebug\n\n      - name: install dependencies\n        run: composer update --${{ matrix.dependency-version }}\n\n      - name: run tests\n        run: php vendor/bin/phpunit\n"
  },
  {
    "path": ".gitignore",
    "content": "vendor\ntest.php\ncomposer.lock\ncomposer.phar\n"
  },
  {
    "path": ".phpdoc-md",
    "content": "<?php\nreturn (object)[\n    'rootNamespace' => 'Wa72\\HtmlPageDom',\n    'destDirectory' => 'doc',\n    'format' => 'github',\n    'classes' => [\n        '\\Wa72\\HtmlPageDom\\HtmlPage',\n        '\\Wa72\\HtmlPageDom\\HtmlPageCrawler'\n    ]\n];\n"
  },
  {
    "path": ".scrutinizer.yml",
    "content": "before_commands:\n    - 'composer install --dev --no-interaction --prefer-source'\n\ntools:\n    # Code Coverage from Travis\n    external_code_coverage:\n        enabled: true\n        timeout: 300\n        filter:\n            excluded_paths:\n                - 'Tests/*'\n                - 'vendor/*'\n    php_code_coverage:\n        enabled: false\n\n    php_code_sniffer:\n        enabled: true\n        config:\n            standard:         PSR2\n        filter:\n            excluded_paths:\n                - 'vendor/*'\n\n    # PHP Mess Detector (http://phpmd.org).\n    php_mess_detector:\n        enabled:              true\n        command:              phpmd\n        config:\n            rulesets:\n                - codesize\n                - unusedcode\n                - design\n        filter:\n            excluded_paths:\n                - 'vendor/*'\n\n    php_pdepend:\n        enabled: true\n        excluded_dirs: [vendor, Tests]\n\n    php_loc:\n        enabled: true\n        excluded_dirs: [vendor, Tests]\n\n    php_cpd:\n        enabled: true\n        excluded_dirs: [vendor, Tests]\n\n    php_analyzer:\n        enabled:              true\n        filter:\n            excluded_paths:\n                - 'Tests/*'\n                - 'vendor/*'\n\n\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "3.0.0\n=====\n\n2022-04-13\n\nChanged 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.\n\nOtherwise there are no changes, so it does not require changes in code using this lib.\n\n2.0.0\n=====\n\n2019-10-15\n\n__BC BREAK__ for compatibility with Symfony 4.3 and up\n\n- `HtmlPageCrawler::html()` is now just the parent `Crawler::html()` and acts as *getter* only.\n  Setting HTML content via `HtmlPageCrawler::html($html)` is *not possible* any more,\n  use `HtmlPageCrawler::setInnerHtml($html)` instead\n\n- `HtmlPageCrawler::text()` is now just the parent `Crawler::text()` and acts as *getter* only\n  that returns the text content from the *first* node only. For setting text content, use `HtmlPageCrawler::setText($text)` instead.\n    \n- `HtmlPageCrawler::attr()` is now just the parent `Crawler::attr()` and acts as *getter* only.\n  For setting attributes use `HtmlPageCrawler::setAttribute($name, $value)` instead\n\n- 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)\n\n- removed method `HtmlPageCrawler::isDisconnected()`\n\n\n1.4.2\n=====\n\n2019-10-15\n\n- undo deprecation of getInnerHtml()\n- deprecate setter use of attr()\n- deprecate isDisconnected()\n\n\n1.4.1\n=====\n\n2019-06-28\n\n- Bugfix: setText() should convert special chars. Closes #34.\n\n\n1.4.0\n=====\n\n2019-05-17\n\nPreparation for a smooth migration to 2.x / Symfony 4.3:\n- deprecate setter use of html() and text(),\n- deprecate getInnerHtml(),\n- new methods setText() and getCombinedText()\n\n\n1.3.2\n=====\n\n2019-04-18\n\n- Mark this version as incompatible to Symfony DomCrawler 4.3\n\n\n1.3\n===\n\n2016-10-06\n\n- new method `unwrapInner` (thanks to [@ttk](https://github.com/ttk))\n\n- it's now possible to get the number of nodes in the crawler using the\n  `$crawler->length` property like in Javascript instead of `count($crawler)`\n\n\n1.2\n===\n\n2015-11-06\n\n- new methods `HtmlPage::minify()` and `HtmlPage::indent()` for compressing or nicely indenting the HTML document. These\n  functions rely on the package `wa72/html-pretty-min` that is *suggested* in composer.json.\n\n1.1\n===\n\n2015-05-20\n\n- `text()` function now returns combined text of all elements in set (as jQuery does; previously only the nodeValue of \n  the first element was returned) and can act as a setter `text($string)` that sets the nodeValue of all elements to\n  the specified string\n\n- function `hasClass` now returns true if any of the elements in the Crawler has the specified class (previously,\n  only the first element was checked). \n\n- new function `makeClone` as equivalent to jQuery's `clone` function (\"clone\" is not a valid function name in PHP).\n  As previously, you can alternatively use PHP's clone operator: `$r = $c->makeClone()` is the same as `$r = clone $c`,\n  but the new function allows chaining.\n\n- new function `removeAttr` aliasing `removeAttribute` for compatibility with jQuery\n\n- `appendTo`, `insertBefore`, `insertAfter`, and `replaceAll` now always return a new Crawler object containing\n  the aggregate set of all elements appended to the target elements (this is the behavior of jQuery 1.9 and newer).\n  \n- `attr` function can now act as setter `attr($name, $value)` which is an alias for `setAttribute($name, $value)`\n  (previously it accepted only one argument and was a getter equivalent to `getAttribute($name)` only, like it is\n  in parent DomCrawler)\n  \n- `attr($name)` and `getAttribute($name)` now always return `null` if the attribute does not exist (previously, an empty\n  string was returned when used with Symfony 2.3)\n\n1.0\n===\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2012-2022 Christoph Singer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nThe software is provided \"as is\", without warranty of any kind, express or\nimplied, including but not limited to the warranties of merchantability,\nfitness for a particular purpose and noninfringement. In no event shall the\nauthors or copyright holders be liable for any claim, damages or other\nliability, whether in an action of contract, tort or otherwise, arising from,\nout of or in connection with the software or the use or other dealings in\nthe software.\n"
  },
  {
    "path": "README.md",
    "content": "HtmlPageDom\n===========\n\n![tests](https://github.com/wasinger/htmlpagedom/actions/workflows/tests.yml/badge.svg?branch=master)\n[![Latest Version](http://img.shields.io/packagist/v/wa72/htmlpagedom.svg)](https://packagist.org/packages/wa72/htmlpagedom)\n[![Downloads from Packagist](http://img.shields.io/packagist/dt/wa72/htmlpagedom.svg)](https://packagist.org/packages/wa72/htmlpagedom)\n\n`Wa72\\HtmlPageDom` is a PHP library for easy manipulation of HTML documents using DOM.\nIt requires [DomCrawler from Symfony components](https://github.com/symfony/DomCrawler) for traversing \nthe DOM tree and extends it by adding methods for manipulating the DOM tree of HTML documents.    \n\nIt's useful when you need to not just extract information from an HTML file (what DomCrawler does) but\nalso to modify HTML pages. It is usable as a template engine: load your HTML template file, set new\nHTML content on certain elements such as the page title, `div#content` or `ul#menu` and print out\nthe modified page.\n\n`Wa72\\HtmlPageDom` consists of two main classes:\n\n-   `HtmlPageCrawler` extends `Symfony\\Components\\DomCrawler` by adding jQuery inspired, HTML specific \n    DOM *manipulation* functions such as `setInnerHtml($htmltext)`, `before()`, `append()`, `wrap()`, `addClass()` or `css()`.\n    It's like jQuery for PHP: simply select elements of an HTML page using CSS selectors and change their \n    attributes and content. \n    \n    [API doc for HtmlPageCrawler](doc/HtmlPageCrawler.md)\n\n-   `HtmlPage` represents one complete HTML document and offers convenience functions like `getTitle()`, `setTitle($title)`,\n    `setMeta('description', $description)`, `getBody()`. Internally, it uses the `HtmlPageCrawler` class for \n    filtering and manipulating DOM Elements. Since version 1.2, it offers methods for compressing (`minify()`) and\n    prettyprinting (`indent()`) the HTML page.\n    \n    [API doc for HtmlPage](doc/HtmlPage.md)\n \n\nRequirements and Compatibility\n------------------------------\n\nVersion 3.x:\n- PHP 8.x\n- [Symfony\\Components\\DomCrawler](https://github.com/symfony/DomCrawler) 6.x | 7.x | 8.x\n- [Symfony\\Components\\CssSelector](https://github.com/symfony/CssSelector) 6.x | 7.x | 8.x\n\nVersion 2.x:\n- PHP ^7.4 | 8.x\n- [Symfony\\Components\\DomCrawler](https://github.com/symfony/DomCrawler) ^4.4 | 5.x\n- [Symfony\\Components\\CssSelector](https://github.com/symfony/CssSelector) ^4.4 | 5.x\n\nThere is no difference in our API between versions 2.x and 3.0.x.\nThe only difference is the compatibility with different versions of Symfony.\n\nInstallation\n------------\n\n-   using [composer](http://getcomposer.org): `composer require wa72/htmlpagedom`\n\n-   using other [PSR-4](http://www.php-fig.org/psr/psr-4/) compliant autoloader:\n    clone this project to where your included libraries are and point your autoloader to look for the \n    \"\\Wa72\\HtmlPageDom\" namespace in the \"src\" directory of this project\n\nUsage\n-----\n\n`HtmlPageCrawler` is a wrapper around DOMNodes. `HtmlPageCrawler` objects can be created using `new` or the static function\n`HtmlPageCrawler::create()`, which accepts an HTML string or a DOMNode (or an array of DOMNodes, a DOMNodeList, or even\nanother `Crawler` object) as arguments.\n\nAfterwards you can select nodes from the added DOM tree by calling `filter()` (equivalent to find() in jQuery) and alter\nthe selected elements using the following jQuery-like manipulation functions:\n\n-   `addClass()`, `hasClass()`, `removeClass()`, `toggleClass()`\n-   `after()`, `before()`\n-   `append()`, `appendTo()`\n-   `makeClone()` (equivalent to `clone()` in jQuery)\n-   `css()` (alias `getStyle()` / `setStyle()`)\n-   `html()` (get inner HTML content) and `setInnerHtml($html)`\n-   `attr()` (alias `getAttribute()` / `setAttribute()`), `removeAttr()`\n-   `insertAfter()`, `insertBefore()`\n-   `makeEmpty()` (equivalent to `empty()` in jQuery)\n-   `prepend()`, `prependTo()`\n-   `remove()`\n-   `replaceAll()`, `replaceWith()`\n-   `text()`, `getCombinedText()` (get text content of all nodes in the Crawler), and `setText($text)`\n-   `wrap()`, `unwrap()`, `wrapInner()`, `unwrapInner()`, `wrapAll()`\n\nTo get the modified DOM as HTML code use `html()` (returns innerHTML of the first node in your crawler object)\nor `saveHTML()` (returns combined \"outer\" HTML code of all elements in the list).\n\nSee the full methods documentation in the generated [API doc for HtmlPageCrawler](doc/HtmlPageCrawler.md)\n\n**Example:**\n\n```php\nuse \\Wa72\\HtmlPageDom\\HtmlPageCrawler;\n\n// create an object from a fragment of HTML code as you would do with jQuery's $() function\n$c = HtmlPageCrawler::create('<div id=\"content\"><h1>Title</h1></div>');\n\n// the above is the same as calling:\n$c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1></div>');\n\n// filter for h1 elements and wrap them with an HTML structure\n$c->filter('h1')->wrap('<div class=\"innercontent\">');\n\n// return the modified HTML\necho $c->saveHTML();\n// or simply:\necho $c; // implicit __toString() calls saveHTML()\n// will output: <div id=\"content\"><div class=\"innercontent\"><h1>Title</h1></div></div>\n```\n\n**Advanced example: remove the third column from an HTML table**\n\n```php\nuse \\Wa72\\HtmlPageDom\\HtmlPageCrawler;\n$html = <<<END\n<table>\n    <tr>\n        <td>abc</td>\n        <td>adsf</td>\n        <td>to be removed</td>\n    </tr>\n    <tr>\n        <td>abc</td>\n        <td>adsf</td>\n        <td>to be removed</td>\n    </tr>\n    <tr>\n        <td>abc</td>\n        <td>adsf</td>\n        <td>to be removed</td>\n    </tr>\n</table>    \nEND;  \n\n$c = HtmlPageCrawler::create($html);\n$tr = $c->filter('table > tr > td')\n    ->reduce(\n        function ($c, $j) {\n            if (($j+1) % 3 == 0) {\n                return true;\n            }\n            return false;\n        }\n    );\n$tr->remove();\necho $c->saveHTML();\n```\n\n**Usage examples for the `HtmlPage` class:**\n\n```php\nuse \\Wa72\\HtmlPageDom\\HtmlPage;\n\n// create a new HtmlPage object with an empty HTML skeleton\n$page = new HtmlPage();\n\n// or create a HtmlPage object from an existing page\n$page = new HtmlPage(file_get_contents('http://www.heise.de'));\n\n// get or set page title\necho $page->getTitle();\n$page->setTitle('New page title');\necho $page->getTitle();\n\n\n// add HTML content\n$page->filter('body')->setInnerHtml('<div id=\"#content\"><h1>This is the headline</h1><p class=\"text\">This is a paragraph</p></div>');\n\n// select elements by css selector\n$h1 = $page->filter('#content h1');\n$p = $page->filter('p.text');\n\n// change attributes and content of an element\n$h1->addClass('headline')->css('margin-top', '10px')->setInnerHtml('This is the <em>new</em> headline');\n\n$p->removeClass('text')->append('<br>There is more than one line in this paragraph');\n\n// add a new paragraph to div#content\n$page->filter('#content')->append('<p>This is a new paragraph.</p>');\n\n// add a class and some attribute to all paragraphs\n$page->filter('p')->addClass('newclass')->setAttribute('data-foo', 'bar');\n\n\n// get HTML content of an element\necho $page->filter('#content')->saveHTML();\n\n// output the whole HTML page\necho $page->save();\n// or simply:\necho $page;\n\n// output formatted HTML code\necho $page->indent()->save();\n\n// output compressed (minified) HTML code\necho $page->minify()->save();\n```\n\nSee also the generated [API doc for HtmlPage](doc/HtmlPage.md)\n\nLimitations\n-----------\n\n- HtmlPageDom builds on top of PHP's DOM functions and uses the loadHTML() and saveHTML() methods of the DOMDocument class.\nThat's why it's output is always HTML, not XHTML.\n\n- The HTML parser used by PHP is built for HTML4. It throws errors \non HTML5 specific elements which are ignored by HtmlPageDom, so HtmlPageDom is usable for HTML5 with some limitations.\n\n- HtmlPageDom has not been tested with character encodings other than UTF-8.\n\n\nHistory\n-------\n\nWhen I discovered how easy it was to modify HTML documents using jQuery I looked for a PHP library providing similar\npossibilities for PHP.\n\nGoogling around I found [SimpleHtmlDom](http://simplehtmldom.sourceforge.net)\nand later [Ganon](http://code.google.com/p/ganon) but both turned out to be very slow. Nevertheless I used both\nlibraries in my projects.\n\nWhen Symfony2 appeared with it's DomCrawler and CssSelector components I thought:\nthe functions for traversing the DOM tree and selecting elements by CSS selectors are already there, only the\nmanipulation functions are missing. Let's implement them! So the HtmlPageDom project was born.\n\nIt turned out that it was a good choice to build on PHP's DOM functions: Compared to SimpleHtmlDom and Ganon, HmtlPageDom\nis lightning fast. In one of my projects, I have a PHP script that takes a huge HTML page containing several hundreds\nof article elements and extracts them into individual HTML files (that are later on demand loaded by AJAX back into the\noriginal HTML page). Using SimpleHtmlDom it took the script 3 minutes (right, minutes!) to run (and I needed to raise\nPHP's memory limit to over 500MB). Using Ganon as HTML parsing and manipulation engine it took even longer,\nabout 5 minutes. After switching to HtmlPageDom the same script doing the same processing tasks is running only about\none second (all on the same server). HtmlPageDom is really fast.\n\n\n© 2012-2023 Christoph Singer. Licensed under the MIT License.\n\n"
  },
  {
    "path": "Resources/jquerytest.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head lang=\"en\">\r\n    <meta charset=\"UTF-8\">\r\n    <script src=\"https://code.jquery.com/jquery-2.1.4.js\"></script>\r\n    <title>Testing jquery object identities</title>\r\n</head>\r\n<body>\r\n<h1>Testing jquery object identities</h1>\r\n<p>This page contains javascript code to figure out in which cases jQuery returns references to existing objects\r\nand when it makes copies.</p>\r\n<p>test paragraph 2<span>555</span></p>\r\n<p>test paragraph 3</p>\r\n\r\n<script>\r\n    (function() {\r\n        if ( typeof Object.prototype.uniqueId == \"undefined\" ) {\r\n            var id = 0;\r\n            Object.prototype.uniqueId = function() {\r\n                if ( typeof this.__uniqueid == \"undefined\" ) {\r\n                    this.__uniqueid = ++id;\r\n                }\r\n                return this.__uniqueid;\r\n            };\r\n        }\r\n    })();\r\n    $(document).ready(function(){\r\n        var $a = $('<span style=\"font-weight: bold;\"> asdf</span>');\r\n        var $b = $('p');\r\n        var $h = $('h1');\r\n        var $ba, $ha;\r\n\r\n        $ba = $a.appendTo($b);\r\n        $ha = $a.appendTo($h);\r\n\r\n        console.log('$a: ' + $a.uniqueId());\r\n        console.log('span: ' + $a[0].uniqueId());\r\n\r\n        console.log('$b: ' + $b.uniqueId());\r\n        console.log($ba);\r\n        console.log('$ba: ' + $ba.uniqueId());\r\n        console.log('$ba span 0: ' + $ba[0].uniqueId());\r\n        console.log('$ba span 1: ' + $ba[1].uniqueId());\r\n        console.log('$ba span 2: ' + $ba[2].uniqueId());\r\n\r\n        console.log('$ha: ' + $ha.uniqueId());\r\n        console.log('$ha span 0: ' + $ha[0].uniqueId());\r\n\r\n        console.log($b.text());\r\n\r\n        $b.text('<span>444</span>');\r\n\r\n        console.log($b.text());\r\n\r\n\r\n        // Test for issue #33 https://github.com/wasinger/htmlpagedom/issues/33\r\n        // Works like reporter expects in jquery but not in HmtlPageDom\r\n\r\n        var $rootNode = $('<div />').appendTo($('body'));\r\n        var $p = $('<p />');\r\n        var $testNode = $('<span />');\r\n        $testNode.text('incorrect text');\r\n        $p.append($testNode);\r\n        $rootNode.append($p);\r\n\r\n        // Change test node text after node appended\r\n        $testNode.text('correct text');\r\n\r\n        // Output root or parent node html. Incorrect in HtmlPageDom, Correct in jquery\r\n        console.log($rootNode.html());\r\n        console.log($p.html());\r\n\r\n        // Output node html. Correct\r\n        console.log($testNode.html());\r\n\r\n        // Second test: adding node to multiple nodes.\r\n        // If $testNode is appended to multple elements it doesn't work in jquery, either:\r\n        $rootNode = $('<div />').appendTo($('body'));\r\n        $p = $('<p /><p />');\r\n        $testNode = $('<span />');\r\n        $testNode.text('incorrect text');\r\n        $p.append($testNode);\r\n        $rootNode.append($p);\r\n\r\n        // Change test node text after node appended\r\n        $testNode.text('correct text');\r\n\r\n        // Output root or parent node html. Incorrect in jquery and HtmlPageDom\r\n        console.log($rootNode.html());\r\n        console.log($p.html());\r\n\r\n        // Output node html. Correct\r\n        console.log($testNode.html());\r\n\r\n    });\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "Tests/HelpersTest.php",
    "content": "<?php\nnamespace Wa72\\HtmlPageDom\\Tests;\n\nuse Wa72\\HtmlPageDom\\Helpers;\nuse org\\bovigo\\vfs\\vfsStream;\nuse PHPUnit\\Framework\\TestCase;\n\nclass HelpersTest extends TestCase\n{\n    public function testCssStringToArray()\n    {\n        $this->assertEquals([\n            'font-size' => '15px',\n            'font-weight' => 'bold',\n            'font-color' => 'black'\n        ], Helpers::cssStringToArray('invalid_css_string;font-size: 15px;font-weight: bold;font-color: black;'));\n    }\n\n    public function testCssArrayToString()\n    {\n        $this->assertEquals('font-size: 15px;font-weight: bold;font-color: black;', Helpers::cssArrayToString([\n            'font-size' => '15px',\n            'font-weight' => 'bold',\n            'font-color' => 'black'\n        ]));\n    }\n}\n"
  },
  {
    "path": "Tests/HtmlPageCrawlerTest.php",
    "content": "<?php\nnamespace Wa72\\HtmlPageDom\\Tests;\n\nuse Wa72\\HtmlPageDom\\HtmlPageCrawler;\nuse PHPUnit\\Framework\\TestCase;\n\nclass HtmlPageCrawlerTest extends TestCase\n{\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::__construct\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::filter\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::getFirstNode\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::nodeName\n     */\n    public function testHtmlPageCrawler()\n    {\n        $c = new HtmlPageCrawler;\n        $c->addHtmlContent('<!doctype html><html><body><div id=\"content\"><h1>Title</h1></div></body></html>');\n        $title = $c->filter('#content > h1');\n\n        $this->assertInstanceOf('\\Wa72\\HtmlPageDom\\HtmlPageCrawler', $title);\n        $this->assertInstanceOf('\\DOMNode', $title->getNode(0));\n        $this->assertEquals('h1', $title->nodeName());\n    }\n\n    /**\n     *\n     *\n     * @param $string\n     * @return string\n     */\n    private function _ignoreNewlines($string)\n    {\n        return str_replace(\"\\n\", '', $string);\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::setInnerHtml\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::prepend\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::makeEmpty\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::setAttribute\n     */\n    public function testManipulationFunctions()\n    {\n        $c = new HtmlPageCrawler;\n        $c->addHtmlContent('<!doctype html><html><body><div id=\"content\"><h1>Title</h1></div></body></html>');\n\n        $content = $c->filter('#content');\n        $content->append('<p>Das ist ein Testabsatz');\n        $this->assertEquals(\"<h1>Title</h1><p>Das ist ein Testabsatz</p>\", $this->_ignoreNewlines($content->html()));\n\n        $content->setInnerHtml('<p>Ein neuer <b>Inhalt</p>');\n        $this->assertEquals('<p>Ein neuer <b>Inhalt</b></p>', $content->html());\n\n        $content->prepend('<h1>Neue Überschrift');\n        $this->assertEquals('<h1>Neue Überschrift</h1><p>Ein neuer <b>Inhalt</b></p>', $content->html());\n\n        $h1 = $content->filter('h1');\n        $this->assertEquals('Neue Überschrift', $h1->text());\n\n        $b = $content->filter('b');\n        $this->assertEquals('Inhalt', $b->text());\n\n        $b2 = $c->filter('#content p b');\n        $this->assertEquals('Inhalt', $b2->text());\n\n        $content->append('<p class=\"a2\">Zweiter Absatz</p>');\n        $content->append('<p class=\"a3\"><b>Dritter Absatz</b> und noch mehr Text</p>');\n\n        $a3 = $content->filter('p.a3');\n        $this->assertEquals('<b>Dritter Absatz</b> und noch mehr Text', $a3->html());\n\n        $a3b = $a3->filter('b');\n        $this->assertEquals('Dritter Absatz', $a3b->text());\n\n        $body = $c->filter('body');\n        $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()));\n\n        $paragraphs = $c->filter('p');\n        $this->assertEquals(3, count($paragraphs));\n\n        $paragraphs->append('<span class=\"appended\">.</span>');\n        $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());\n\n        $body->makeEmpty();\n        $this->assertEmpty($body->html());\n\n        $body->setAttribute('class', 'mybodyclass');\n        $this->assertEquals('mybodyclass', $body->attr('class'));\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::append\n     */\n    public function testAppend()\n    {\n        // Testing append string to several elements\n        $c = new HtmlPageCrawler('<p>Paragraph 1</p><p>Paragraph 2</p><p>Paragraph 3</p>');\n        $c->filter('p')->append('<br>Appended Text');\n        $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());\n\n        // Testing append HtmlPageCrawler to several elements\n        $c = new HtmlPageCrawler('<p>Paragraph 1</p><p>Paragraph 2</p><p>Paragraph 3</p>');\n        $c->filter('p')->append(new HtmlPageCrawler('<br>Appended Text'));\n        $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());\n\n        // Testing append DOMNode to several elements\n        $c = new HtmlPageCrawler('<p>Paragraph 1</p><p>Paragraph 2</p><p>Paragraph 3</p>');\n        $app = $c->getDOMDocument()->createElement('span', 'Appended Text');\n        $c->filter('p')->append($app);\n        $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());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><span>Append Self</span></div>');\n        $c->filter('#content')->append($c->filter('span'));\n        $this->assertEquals('<div id=\"content\"><span>Append Self</span></div>', $c->saveHTML());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::appendTo\n     */\n    public function testAppendTo()\n    {\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1><em>Big</em></div>');\n        $c->filter('em')->appendTo($c->filter('h1'));\n        $this->assertEquals('<div id=\"content\"><h1>Title<em>Big</em></h1></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Self Title</h1></div>');\n        $c->filter('h1')->appendTo($c->filter('h1'));\n        $this->assertEquals('<div id=\"content\"><h1>Self Title</h1></div>', $c->saveHTML());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::isHtmlDocument\n     */\n    public function testIsHtmlDocument()\n    {\n        $dom = new \\DOMDocument('1.0', 'UTF-8');\n        $dom->loadHTML('<!DOCTYPE html><html><body><div id=\"content\"><h1>Title</h1></div></body></html>');\n        $c = new HtmlPageCrawler($dom);\n\n        $this->assertTrue($c->isHtmlDocument());\n\n        $t = $c->filter('body');\n        $this->assertFalse($t->isHtmlDocument());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1></div>');\n        $this->assertFalse($c->isHtmlDocument());\n\n        $c = new HtmlPageCrawler('<html><body><div id=\"content\"><h1>Title</h1></div></body></html>');\n        $this->assertTrue($c->isHtmlDocument());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::saveHTML\n     */\n    public function testSaveHTML()\n    {\n        $html = \"<!DOCTYPE html><html><body><h1>Title</h1><p>Paragraph 1</p><p>Paragraph 2</p></body></html>\";\n        $dom = new \\DOMDocument('1.0', 'UTF-8');\n        $dom->loadHTML($html);\n        $c = new HtmlPageCrawler($dom);\n        $this->assertEquals($html, $this->_ignoreNewlines($c->saveHTML()));\n        $ps = $c->filter('p');\n        $this->assertEquals('<p>Paragraph 1</p><p>Paragraph 2</p>', $ps->saveHTML());\n        $t = $c->filter('h1');\n        $this->assertEquals('<h1>Title</h1>', $t->saveHTML());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1></div>');\n        $this->assertEquals('<div id=\"content\"><h1>Title</h1></div>', $c->saveHTML());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::css\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::getStyle\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::setStyle\n     */\n    public function testCss()\n    {\n        $dom = new \\DOMDocument('1.0', 'UTF-8');\n        $dom->loadHTML('<!DOCTYPE html><html><body><div id=\"content\"><h1 style=\" margin-top:\n         10px;border-bottom:  1px solid red\">Title</h1></div></body></html>');\n        $c = new HtmlPageCrawler($dom);\n        $t = $c->filter('h1');\n        $this->assertEquals('10px', $t->css('margin-top'));\n        $this->assertEquals('1px solid red', $t->css('border-bottom'));\n        $t->css('margin-bottom', '20px');\n        $this->assertEquals('20px', $t->css('margin-bottom'));\n        $this->assertEquals('10px', $t->getStyle('margin-top'));\n        $this->assertEquals('<h1 style=\"margin-top: 10px;border-bottom: 1px solid red;margin-bottom: 20px;\">Title</h1>', $t->saveHTML());\n        $t->setStyle('border-bottom', '');\n        $this->assertEquals('<h1 style=\"margin-top: 10px;margin-bottom: 20px;\">Title</h1>', $t->saveHTML());\n        $t->setStyle('padding-top', '0');\n        $this->assertEquals('<h1 style=\"margin-top: 10px;margin-bottom: 20px;padding-top: 0;\">Title</h1>', $t->saveHTML());\n        $this->assertEquals('0', $t->getStyle('padding-top'));\n        $this->assertNull($t->getStyle('border-bottom'));\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::addClass\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::removeClass\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::hasClass\n     */\n    public function testClasses()\n    {\n        $dom = new \\DOMDocument('1.0', 'UTF-8');\n        $dom->loadHTML('<!DOCTYPE html><html><body><div id=\"content\"><h1 class=\"style_class\">Title</h1></div></body></html>');\n        $c = new HtmlPageCrawler($dom);\n        $t = $c->filter('h1');\n        $t->addClass('ueberschrift');\n        $t->addClass('nochneklasse');\n        $t->addClass('style_class');\n        $this->assertEquals('<h1 class=\"style_class ueberschrift nochneklasse\">Title</h1>', $t->saveHTML());\n        $this->assertTrue($t->hasClass('ueberschrift'));\n        $this->assertTrue($t->hasClass('nochneklasse'));\n        $this->assertTrue($t->hasClass('style_class'));\n        $t->removeClass('nochneklasse');\n        $this->assertTrue($t->hasClass('ueberschrift'));\n        $this->assertFalse($t->hasClass('nochneklasse'));\n        $t->addClass('class1 class2');\n        $this->assertTrue($t->hasClass('class1'));\n        $this->assertTrue($t->hasClass('class2'));\n\n        $c1 = new HtmlPageCrawler('<p class=\"a\"></p><p class=\"b\"></p><p class=\"c\"></p>');\n        $this->assertTrue($c1->hasClass('b'));\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::addContent\n     */\n    public function testAddContent()\n    {\n        $c = new HtmlPageCrawler();\n        $c->addContent('<html><head></head><body><div id=\"content\"><h1>Title</h1></div></body>');\n\n        // The behaviour of Symfony's \\Symfony\\Component\\DomCrawler\\Crawler varies based on Symfony version and PHP Version.\n        // So for this test, we normalize the output.\n\n        $html = $c->saveHTML();\n\n        // Old parser (PHP < 8.4) or old Symfony: remove added HTML 4.0 DOCTYPE at the beginning of the html\n        $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);\n\n        // Remove XML encoding declaration added by Symfony's UTF-8 handling (varies by Symfony version)\n        $html = preg_replace('#^\\s*<\\?xml encoding=\"UTF-8\">\\s*#', '', $html);\n        $html = $this->_ignoreNewlines($html);\n\n        $this->assertEquals(\n          '<html><head></head><body><div id=\"content\"><h1>Title</h1></div></body></html>',\n          $html\n        );\n\n        $c = new HtmlPageCrawler();\n        $c->addContent('<div id=\"content\"><h1>Title');\n        $this->assertEquals('<div id=\"content\"><h1>Title</h1></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler();\n        $c->addContent('<p>asdf<p>asdfaf</p>');\n        $this->assertEquals(2, count($c));\n        $this->assertEquals('<p>asdf</p><p>asdfaf</p>', $c->saveHTML());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::before\n     */\n    public function testBefore()\n    {\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1></div>');\n        $c->filter('h1')->before('<p>Text before h1</p>');\n        $this->assertEquals('<div id=\"content\"><p>Text before h1</p><h1>Title</h1></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1></div>');\n        $c->filter('h1')->before(new HtmlPageCrawler('<p>Text before h1</p><p>and more text before</p>'));\n        $this->assertEquals('<div id=\"content\"><p>Text before h1</p><p>and more text before</p><h1>Title</h1></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Self Before</h1></div>');\n        $c->filter('h1')->before($c->filter('h1'));\n        $this->assertEquals('<div id=\"content\"><h1>Self Before</h1></div>', $c->saveHTML());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::insertBefore\n     */\n    public function testInsertBefore()\n    {\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1><p>Text before h1</p></div>');\n        $c->filter('p')->insertBefore($c->filter('h1'));\n        $this->assertEquals('<div id=\"content\"><p>Text before h1</p><h1>Title</h1></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Self Insert Before Title</h1><p>Text after h1</p></div>');\n        $c->filter('h1')->insertBefore($c->filter('h1'));\n        $this->assertEquals('<div id=\"content\"><h1>Self Insert Before Title</h1><p>Text after h1</p></div>', $c->saveHTML());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::after\n     */\n    public function testAfter()\n    {\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1></div>');\n        $c->filter('h1')->after('<p>Text after h1</p>');\n        $this->assertEquals('<div id=\"content\"><h1>Title</h1><p>Text after h1</p></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1><h1>Title2</h1></div>');\n        $c->filter('h1')->after(new HtmlPageCrawler('<p>Text after h1</p><p>and more text after</p>'));\n        $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());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Self After</h1></div>');\n        $c->filter('h1')->after($c->filter('h1'));\n        $this->assertEquals('<div id=\"content\"><h1>Self After</h1></div>', $c->saveHTML());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::insertAfter\n     */\n    public function testInsertAfter()\n    {\n        $c = new HtmlPageCrawler('<div id=\"content\"><p>Text after h1</p><h1>Title</h1></div>');\n        $c->filter('p')->insertAfter($c->filter('h1'));\n        $this->assertEquals('<div id=\"content\"><h1>Title</h1><p>Text after h1</p></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><p>Text before h1</p><h1>Self Insert After Title</h1></div>');\n        $c->filter('h1')->insertAfter($c->filter('h1'));\n        $this->assertEquals('<div id=\"content\"><p>Text before h1</p><h1>Self Insert After Title</h1></div>', $c->saveHTML());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::prepend\n     */\n    public function testPrepend()\n    {\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1></div>');\n        $c->filter('#content')->prepend('<p>Text before h1</p>');\n        $this->assertEquals('<div id=\"content\"><p>Text before h1</p><h1>Title</h1></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"></div>');\n        $c->filter('#content')->prepend(new HtmlPageCrawler('<p>Text before h1</p><p>and more text before</p>'));\n        $this->assertEquals('<div id=\"content\"><p>Text before h1</p><p>and more text before</p></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><span>Prepend Self</span></div>');\n        $c->filter('#content')->prepend($c->filter('span'));\n        $this->assertEquals('<div id=\"content\"><span>Prepend Self</span></div>', $c->saveHTML());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::prependTo\n     */\n    public function testPrependTo()\n    {\n        $c = new HtmlPageCrawler('<div id=\"content\"><p>Text before</p></div>');\n        $c->filter('p')->prependTo('Text');\n        $this->assertEquals('<div id=\"content\"><p>Text before</p></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1></div>');\n        $c->filter('#content')->prependTo(new HtmlPageCrawler('<p>paragraph</p>'));\n        $this->assertEquals('<div id=\"content\"><h1>Title</h1></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1><em>Big</em></div>');\n        $c->filter('em')->prependTo($c->filter('h1'));\n        $this->assertEquals('<div id=\"content\"><h1><em>Big</em>Title</h1></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Self Title</h1></div>');\n        $c->filter('h1')->prependTo($c->filter('h1'));\n        $this->assertEquals('<div id=\"content\"><h1>Self Title</h1></div>', $c->saveHTML());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::wrap\n     */\n    public function testWrap()\n    {\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1></div>');\n        $c->filter('h1')->wrap('<div class=\"innercontent\">');\n        $this->assertEquals('<div id=\"content\"><div class=\"innercontent\"><h1>Title</h1></div></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1></div>');\n        $c->filter('h1')->wrap('<div class=\"ic\">asdf<div class=\"a1\"><div class=\"a2\"></div></div></div></div>');\n        $this->assertEquals('<div id=\"content\"><div class=\"ic\">asdf<div class=\"a1\"><div class=\"a2\"><h1>Title</h1></div></div></div></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler('<div id=\"content\"><h1>Title</h1></div>');\n        $c->filter('h1')->wrap('<div class=\"ic\">asdf</div><div>jkl</div>'); // wrap has more than 1 root element\n        $this->assertEquals('<div id=\"content\"><div class=\"ic\">asdf<h1>Title</h1></div></div>', $c->saveHTML()); // only first element is used\n\n        // Test for wrapping multiple nodes\n        $c = new HtmlPageCrawler('<div id=\"content\"><p>p1</p><p>p2</p></div>');\n        $c->filter('p')->wrap('<div class=\"p\"></div>');\n        $this->assertEquals('<div id=\"content\"><div class=\"p\"><p>p1</p></div><div class=\"p\"><p>p2</p></div></div>', $c->saveHTML());\n\n        $c = new HtmlPageCrawler('plain text node');\n        $c->wrap('<div class=\"ic\"></div>');\n        $this->assertEquals('<div class=\"ic\">plain text node</div>', $c->ancestors()->eq(0)->saveHTML());\n\n        $c = HtmlPageCrawler::create('<div>');\n        $m = HtmlPageCrawler::create('message 1')->appendTo($c);\n        $m->wrap('<p>');\n        $m = HtmlPageCrawler::create('message 2')->appendTo($c);\n        $m->wrap('<p>');\n        $this->assertEquals('<div><p>message 1</p><p>message 2</p></div>', $c->saveHTML());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::replaceWith\n     */\n    public function testReplaceWith()\n    {\n        $c = HtmlPageCrawler::create('<div id=\"content\"><p>Absatz 1</p><p>Absatz 2</p><p>Absatz 3</p></div>');\n        $oldparagraphs = $c->filter('p')->replaceWith('<div>newtext 1</div><div>newtext 2</div>');\n        $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());\n        $this->assertEquals('<p>Absatz 1</p><p>Absatz 2</p><p>Absatz 3</p>', $oldparagraphs->saveHTML());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::replaceAll\n     */\n    public function testReplaceAll()\n    {\n        $c = HtmlPageCrawler::create('<div id=\"content\"><p>Absatz 1</p><p>Absatz 2</p><p>Absatz 3</p></div>');\n        $new = HtmlPageCrawler::create('<div>newtext 1</div><div>newtext 2</div>');\n        $new->replaceAll($c->filter('p'));\n        $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());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::wrapAll\n     */\n    public function testWrapAll()\n    {\n        $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>');\n        $c->filter('p')->wrapAll('<div class=\"a\">');\n        $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());\n\n        // Test for wrapping with elements that have children\n        $c = HtmlPageCrawler::create('<div id=\"content\"><p>Absatz 1</p><p>Absatz 2</p><p>Absatz 3</p></div>');\n        $c->filter('p')->wrapAll('<article><section><div class=\"a\"></div></section></article>');\n        $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());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::wrapInner\n     */\n    public function testWrapInner()\n    {\n        $c = HtmlPageCrawler::create('<div id=\"content\"><p>Absatz 1</p><p>Absatz 2</p><p>Absatz 3</p></div>');\n        $c->wrapInner('<div class=\"a\">');\n        $this->assertEquals('<div id=\"content\"><div class=\"a\"><p>Absatz 1</p><p>Absatz 2</p><p>Absatz 3</p></div></div>', $c->saveHTML());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::unwrap\n     */\n    public function testUnwrap()\n    {\n        $c = HtmlPageCrawler::create('<div id=\"content\"><div>Before</div><div class=\"a\"><p>Absatz 1</p></div><div>After</div></div>');\n        $p = $c->filter('p');\n        $p->unwrap();\n        $this->assertEquals('<div id=\"content\"><div>Before</div><p>Absatz 1</p><div>After</div></div>', $c->saveHTML());\n    }\n\n    public function testUnwrapInnerOnDOMElementException()\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n        $this->expectExceptionMessage('DOMElement does not have a parent DOMElement node.');\n\n        $c = HtmlPageCrawler::create('<div id=\"content\"></div>');\n        $p = $c->filter('div#content');\n        $p->unwrapInner();\n        $p->unwrapInner();\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::unwrapInner\n     */\n    public function testUnwrapInner()\n    {\n        $c = HtmlPageCrawler::create('<div id=\"content\"><div>Before</div><div class=\"a\"><p>Absatz 1</p></div><div>After</div></div>');\n        $p = $c->filter('div.a');\n        $p->unwrapInner();\n        $this->assertEquals('<div id=\"content\"><div>Before</div><p>Absatz 1</p><div>After</div></div>', $c->saveHTML());\n    }\n\n    /**\n     * @covers Wa72\\HtmlPageDom\\HtmlPageCrawler::toggleClass\n     */\n    public function testToggleClass()\n    {\n        $c = HtmlPageCrawler::create('<div id=\"1\" class=\"a c\"><div id=\"2\" class=\"b c\"></div></div>');\n        $c->filter('div')->toggleClass('a d')->toggleClass('b');\n        $this->assertEquals('<div id=\"1\" class=\"c d b\"><div id=\"2\" class=\"c a d\"></div></div>', $c->saveHTML());\n    }\n\n    public function testRemove()\n    {\n        // remove every third td in tbody\n        $html = <<<END\n<table>\n    <thead>\n    <tr>\n        <th>A</th>\n        <th>B</th>\n    </tr>\n    </thead>\n    <tbody>\n    <tr class=\"r1\">\n        <td class=\"c11\">16.12.2013</td>\n        <td class=\"c12\">asdf asdf</td>\n        <td class=\"c13\">&nbsp;</td>\n    </tr>\n    <tr class=\"r2\">\n        <td class=\"c21\">02.12.2013 16:30</td>\n        <td class=\"c22\">asdf asdf</td>\n        <td class=\"c23\">&nbsp;</td>\n    </tr>\n    <tr class=\"r3\">\n        <td class=\"c31\">25.11.2013 16:30</td>\n        <td class=\"c32\">asdf asdf</td>\n        <td class=\"c33\">&nbsp;</td>\n    </tr>\n    <tr class=\"r4\">\n        <td class=\"c41\">18.11.2013 16:30</td>\n        <td class=\"c42\">asdf asdf</td>\n        <td class=\"c43\">&nbsp;</td>\n    </tr>\n    <tr class=\"r5\">\n        <td class=\"c51\">24.10.2013 16:30</td>\n        <td class=\"c52\">asdf asdf</td>\n        <td class=\"c53\">&nbsp;</td>\n    </tr>\n    <tr class=\"r6\">\n        <td class=\"c61\">10.10.2013 16:30</td>\n        <td class=\"c62\">asdf asdf</td>\n        <td class=\"c63\">&nbsp;</td>\n    </tr>\n</table>\nEND;\n        $c = HtmlPageCrawler::create($html);\n        $this->assertEquals(1, count($c->filter('td.c23')));\n        $tbd = $c->filter('table > tbody > tr > td')\n            ->reduce(\n                function ($c, $j) {\n                    if (($j+1) % 3 == 0) {\n                        return true;\n                    }\n                    return false;\n                }\n            );\n        $this->assertEquals(6, count($tbd));\n        $tbd->remove();\n        $this->assertEquals(0, count($tbd));\n        $this->assertEquals(0, count($c->filter('td.c23')));\n    }\n\n    public function testUTF8Characters()\n    {\n        $text = file_get_contents(__DIR__ . '/utf8.html');\n        $c = HtmlPageCrawler::create($text);\n\n        $expected =<<< END\n<p style=\"margin: 0cm 0cm 0pt;\"><span>Die Burse&nbsp;wurde unmittelbar (1478 bis 1482) nach der Universit&auml;tsgr&uuml;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&auml;ude im Stil des Klassizismus zum ersten T&uuml;binger Klinikum umgebaut. Einer der ersten Patienten war Friedrich H&ouml;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 &ndash; r&uuml;ckw&auml;rts gewandt &ndash; einen guten Blick auf die Stadtbefestigung mit \"Pechnasen\" und Spuren des alten Wehrgangs.</span></p>\nEND;\n\n        $this->assertEquals($expected, $c->filter('p')->saveHTML());\n    }\n\n    public function testAttr()\n    {\n        $c = HtmlPageCrawler::create('<div>');\n        $this->assertNull($c->attr('data-foo'));\n        $c->setAttribute('data-foo', 'bar');\n        $this->assertEquals('bar', $c->attr('data-foo'));\n        $this->assertEquals('bar', $c->getAttribute('data-foo'));\n        $c->removeAttribute('data-foo');\n        $this->assertNull($c->attr('data-foo'));\n        $c->setAttribute('data-foo', 'bar');\n        $this->assertEquals('bar', $c->attr('data-foo'));\n        // getAttribute is just an alias to attr() and should provide the same result\n        $this->assertEquals('bar', $c->getAttribute('data-foo'));\n        $c->removeAttr('data-foo');\n        $this->assertNull($c->attr('data-foo'));\n\n    }\n\n    public function testAttrOnInvalidNodeList()\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n        $c = HtmlPageCrawler::create(null);\n        $c->attr('data-foo');\n    }\n\n    public function testSetInnerHtml()\n    {\n        $html = HtmlPageCrawler::create('<h1>Title</h1>');\n        $this->assertInstanceOf('Wa72\\HtmlPageDom\\HtmlPageCrawler', $html->setInnerHtml('<h2>Title</h2>'));\n        $this->assertEquals('<h2>Title</h2>', $html->html());\n        // getInnerHtml is just an alias for html() and should provide the same result\n        $this->assertEquals('<h2>Title</h2>', $html->getInnerHtml());\n    }\n\n    public function testToString()\n    {\n        $html = HtmlPageCrawler::create('<h2>Title</h2>');\n        $this->assertEquals('<h2>Title</h2>', (string) $html);\n    }\n\n    public function testGetDOMDocument()\n    {\n        $html = HtmlPageCrawler::create('<h2>Title</h2>');\n        $this->assertInstanceOf('\\DOMDocument', $html->getDOMDocument());\n    }\n\n    public function testAddOnCrawlerInstance()\n    {\n        $html = HtmlPageCrawler::create('<h1>Title</h1>');\n        $html->add($html);\n        $this->assertEquals('<h1>Title</h1>', (string) $html);\n    }\n\n    public function testReturnValues()\n    {\n        // appendTo, insertBefore, insertAfter, replaceAll should always return new Crawler objects\n        // see http://jquery.com/upgrade-guide/1.9/#appendto-insertbefore-insertafter-and-replaceall\n\n        $c1 = HtmlPageCrawler::create('<h1>Headline</h1>');\n        $c2 = HtmlPageCrawler::create('<p>1</p><p>2</p><p>3</p>');\n        $c3 = HtmlPageCrawler::create('<span>asdf</span>');\n\n        $r1 = $c3->appendTo($c1);\n        $this->assertNotEquals(spl_object_hash($c3), spl_object_hash($r1));\n\n        $r2 = $c3->insertBefore($c1);\n        $this->assertNotEquals(spl_object_hash($c3), spl_object_hash($r2));\n\n        $r3 = $c3->insertAfter($c1);\n        $this->assertNotEquals(spl_object_hash($c3), spl_object_hash($r3));\n\n        $r4 = $c3->replaceAll($c1);\n        $this->assertNotEquals(spl_object_hash($c3), spl_object_hash($r4));\n\n\n        $r1 = $c3->appendTo($c2);\n        $this->assertNotEquals(spl_object_hash($c2), spl_object_hash($r1));\n\n        $r2 = $c3->insertBefore($c2);\n        $this->assertNotEquals(spl_object_hash($c2), spl_object_hash($r2));\n\n        $r3 = $c3->insertAfter($c2);\n        $this->assertNotEquals(spl_object_hash($c2), spl_object_hash($r3));\n\n        $r4 = $c3->replaceAll($c2);\n        $this->assertNotEquals(spl_object_hash($c2), spl_object_hash($r4));\n\n    }\n\n    public function testDisconnectedNodes()\n    {\n        // if after(), before() or replaceWith() is called on a node without parent,\n        // the unmodified Crawler object should be returned\n        //\n        // see http://jquery.com/upgrade-guide/1.9/#after-before-and-replacewith-with-disconnected-nodes\n        $c = HtmlPageCrawler::create('<div>abc</div>');\n        $r = HtmlPageCrawler::create('<div>def</div>');\n\n        $r1 = $c->after($r);\n        $this->assertEquals(spl_object_hash($r1), spl_object_hash($c));\n        $this->assertEquals(count($r1), count($c));\n\n        $r2 = $c->before($r);\n        $this->assertEquals(spl_object_hash($r2), spl_object_hash($c));\n        $this->assertEquals(count($r2), count($c));\n\n        $r3 = $c->replaceWith($r);\n        $this->assertEquals(spl_object_hash($r3), spl_object_hash($c));\n        $this->assertEquals(count($r3), count($c));\n    }\n\n    public function testClone()\n    {\n        $c = HtmlPageCrawler::create('<div><p class=\"x\">asdf</p></div>');\n        $p = $c->filter('p');\n\n        $p1 = $p->makeClone();\n        $this->assertNotEquals(spl_object_hash($p), spl_object_hash($p1));\n        $this->assertTrue($p1->hasClass('x'));\n        $p1->removeClass('x');\n        $this->assertTrue($p->hasClass('x'));\n        $this->assertFalse($p1->hasClass('x'));\n        $p->after($p1);\n        $this->assertEquals('<div><p class=\"x\">asdf</p><p class=\"\">asdf</p></div>', $c->saveHTML());\n    }\n\n    public function testGetCombinedText()\n    {\n        $c = HtmlPageCrawler::create('<p>abc</p><p>def</p>');\n        $this->assertEquals('abcdef', $c->getCombinedText());\n        $c->setText('jklo');\n        $this->assertEquals('jklojklo', $c->getCombinedText());\n    }\n\n    public function testSetText()\n    {\n        $c = HtmlPageCrawler::create('<div>&quot;</div>');\n        $this->assertEquals('\"', $c->text());\n        $c->setText('&');\n        $this->assertEquals('&', $c->text());\n    }\n\n    public function testMagicGet()\n    {\n        // $crawler->length should give us the number of nodes in the crawler\n        $c = HtmlPageCrawler::create('<p>abc</p><p>def</p>');\n        $this->assertEquals(2, $c->length);\n\n        // not existing property throws exception\n        try {\n            $c->foo;\n        } catch (\\Exception $e) {\n            $this->assertEquals('No such property foo', $e->getMessage());\n            return;\n        }\n        $this->fail();\n    }\n}\n"
  },
  {
    "path": "Tests/HtmlPageTest.php",
    "content": "<?php\nnamespace Wa72\\HtmlPageDom\\Tests;\n\nuse Wa72\\HtmlPageDom\\HtmlPage;\nuse org\\bovigo\\vfs\\vfsStream;\nuse PHPUnit\\Framework\\TestCase;\n\nclass HtmlPageTest extends TestCase\n{\n    public function setUp(): void\n    {\n        $this->root = vfsStream::setup('root');\n    }\n\n    public function testHtmlPage()\n    {\n        $hp = new HtmlPage;\n        $this->assertEquals(\"<!DOCTYPE html>\\n<html><head><title></title></head><body></body></html>\\n\", $hp->__toString());\n\n        $title = 'Erste Testseite';\n        $hp->setTitle($title);\n        $this->assertEquals($title, $hp->getTitle());\n\n        $title = 'Seite \"schön & gut\" >> so wird\\'s, süß';\n        $hp->setTitle($title);\n        $this->assertEquals($title, $hp->getTitle());\n\n        $description = 'Dies ist die erste \"Testseite\" >> so wird\\'s, süß';\n        $hp->setMeta('description', $description);\n        $this->assertEquals($description, $hp->getMeta('description'));\n\n        $hp->removeMeta('description');\n        $this->assertNull($hp->getMeta('description'));\n\n        $bodycontent = '<div id=\"content\">Testcontent1</div>';\n        $body = $hp->filter('body');\n        $body->setInnerHtml($bodycontent);\n        $this->assertEquals($bodycontent, $body->html());\n        $this->assertEquals($bodycontent, $hp->filter('body')->html());\n\n        $content = \"<h1>Überschrift</h1>\\n<p>bla bla <br><b>fett</b></p>\";\n        $hp->setHtmlById('content', $content);\n        // echo $hp;\n        $this->assertEquals($content, $hp->getElementById('content')->html());\n\n        $url = 'http://www.tuebingen.de/';\n        $hp->setBaseHref($url);\n        $this->assertEquals($url, $hp->getBaseHref());\n    }\n\n\n    public function testClone()\n    {\n        $hp = new HtmlPage;\n        $this->assertEquals(\"<!DOCTYPE html>\\n<html><head><title></title></head><body></body></html>\\n\", $hp->__toString());\n\n        $title = 'Erste Testseite';\n        $hp->setTitle($title);\n        $this->assertEquals($title, $hp->getTitle());\n\n        $hp2 = clone $hp;\n\n        $newtitle = 'Seitentitel neu';\n        $hp->setTitle($newtitle);\n\n        $this->assertEquals($title, $hp2->getTitle());\n        $this->assertEquals($newtitle, $hp->getTitle());\n    }\n\n    public function testScript()\n    {\n        $html =<<<END\n<!DOCTYPE html>\n<html>\n<head>\n<title></title>\n<script>\n// this will be awesome\nalert('Hello world');\n</script>\n</head>\n<body>\n</body>\n</html>\n\nEND;\n        $hp = new HtmlPage($html);\n        $hp->getBody()->append('<h1>Script Test</h1>');\n        $newhtml = $hp->save();\n\n        $expected =<<<END\n<!DOCTYPE html>\n<html>\n<head>\n<title></title>\n<script>\n// this will be awesome\nalert('Hello world');\n</script>\n</head>\n<body>\n<h1>Script Test</h1></body>\n</html>\n\nEND;\n        $this->assertEquals($expected, $newhtml);\n\n    }\n\n    public function testMinify()\n    {\n        $html =<<<END\n<!DOCTYPE html>\n<html>\n<head>\n<title></title>\n<script>\n// this will be awesome\nalert('Hello world');\n</script>\n</head>\n<body>\n    <h1>TEST</h1>\n    <p class=\"\">\n    asdf jksdlf ajsfk\n    <b>jasdf\n    jaksfd asdf</b>\n    <a>jasdf jaks</a>\n    </p>\n</body>\n</html>\n\nEND;\n        $hp = new HtmlPage($html);\n\n        $expected = <<<END\n<!DOCTYPE html>\n<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>\n\nEND;\n        $this->assertEquals($expected, $hp->minify()->save());\n\n    }\n\n    public function testIndent()\n    {\n        $html =<<<END\n<!DOCTYPE html>\n<html>\n<head>\n<title></title>\n<script>\n// this will be awesome\nalert('Hello world');\n</script>\n</head>\n<body>\n    <h1>TEST</h1>\n    <p>\n    asdf jksdlf ajsfk\n    <b>jasdf\n    jaksfd asdf</b>\n    <a>jasdf jaks</a>\n    </p>\n</body>\n</html>\n\nEND;\n        $hp = new HtmlPage($html);\n\n        $expected = <<<END\n<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title></title>\n\t\t<script>\n// this will be awesome\nalert('Hello world');\n\t\t</script>\n\t</head>\n\t<body>\n\t\t<h1>TEST</h1>\n\t\t<p>asdf jksdlf ajsfk <b>jasdf jaksfd asdf</b> <a>jasdf jaks</a></p>\n\t</body>\n</html>\n\nEND;\n        $this->assertEquals($expected, $hp->indent()->save());\n\n    }\n\n    public function testGetCrawler()\n    {\n        $html = <<<END\n<!DOCTYPE html>\n<html>\n<head>\n<title></title>\n<script>\n// this will be awesome\nalert('Hello world');\n</script>\n</head>\n<body>\n    <h1>TEST</h1>\n    <p class=\"\">\n    asdf jksdlf ajsfk\n    <b>jasdf\n    jaksfd asdf</b>\n    <a>jasdf jaks</a>\n    </p>\n</body>\n</html>\n\nEND;\n\n        $hp = new HtmlPage($html);\n        $this->assertEquals('<h1>TEST</h1>', $hp->getCrawler()->filter('h1')->saveHtml());\n    }\n\n    public function testGetDOMDocument()\n    {\n        $html = <<<END\n<!DOCTYPE html>\n<html>\n<head>\n<title></title>\n<script>\n// this will be awesome\nalert('Hello world');\n</script>\n</head>\n<body>\n    <h1>TEST</h1>\n    <p class=\"\">\n    asdf jksdlf ajsfk\n    <b>jasdf\n    jaksfd asdf</b>\n    <a>jasdf jaks</a>\n    </p>\n</body>\n</html>\n\nEND;\n\n        $hp = new HtmlPage($html);\n        $this->assertInstanceOf('\\DOMDocument', $hp->getDOMDocument());\n    }\n\n    public function testSetTitleOnNoTitleElement()\n    {\n        $html = <<<END\n<!DOCTYPE html>\n<html>\n<head>\n<script>\n// this will be awesome\nalert('Hello world');\n</script>\n</head>\n<body>\n    <h1>TEST</h1>\n    <p class=\"\">\n    asdf jksdlf ajsfk\n    <b>jasdf\n    jaksfd asdf</b>\n    <a>jasdf jaks</a>\n    </p>\n</body>\n</html>\n\nEND;\n\n        $hp = new HtmlPage($html);\n        $hp->setTitle('TEST');\n        $this->assertEquals('TEST', $hp->getTitle());\n    }\n\n    public function testGetTitleShouldReturnNull()\n    {\n        $html = <<<END\n<!DOCTYPE html>\n<html>\n<head>\n<script>\n// this will be awesome\nalert('Hello world');\n</script>\n</head>\n<body>\n    <h1>TEST</h1>\n    <p class=\"\">\n    asdf jksdlf ajsfk\n    <b>jasdf\n    jaksfd asdf</b>\n    <a>jasdf jaks</a>\n    </p>\n</body>\n</html>\n\nEND;\n\n        $hp = new HtmlPage($html);\n        $this->assertNull($hp->getTitle());\n    }\n\n    public function testGetBaseHrefShouldReturnNull()\n    {\n        $hp = new HtmlPage('<!DOCTYPE html><html><head><title>TEST</title></head><body>Hello</body></html>');\n        $this->assertNull($hp->getBaseHref());\n    }\n\n    public function testGetHeadNodeShouldAddTheHeadTag()\n    {\n        $hp = new HtmlPage('<!DOCTYPE html><html><body>Hello</body></html>');\n        $this->assertInstanceOf('\\DOMElement', $hp->getHeadNode());\n        $this->assertEquals('<head></head>', (string) $hp->getHead());\n    }\n\n    public function testGetBodyNodeShouldAddTheBodyTag()\n    {\n        $hp = new HtmlPage('<!DOCTYPE html><html></html>');\n        $this->assertInstanceOf('\\DOMElement', $hp->getBodyNode());\n        $this->assertEquals('<body></body>', (string) $hp->getBody());\n    }\n\n    public function testTrimNewlines()\n    {\n        $html = <<<END\n<!DOCTYPE html>\n<html>\n    <head>\n    <title>TEST</title>\n    </head>\n</html>\nEND;\n\n        $this->assertEquals('<!DOCTYPE html> <html> <head> <title>TEST</title> </head> </html>', (string) HtmlPage::trimNewlines($html));\n    }\n\n    public function testSaveOnFileName()\n    {\n        $hp = new HtmlPage('<!DOCTYPE html><html><head><title>TEST</title></head></html>');\n        $hp->save(vfsStream::url('root/save.html'));\n        $this->assertFileExists(vfsStream::url('root/save.html'));\n    }\n\n    public function testEmbeddedScriptWithHtml()\n    {\n        // PHP DOMDocument->loadHTML method tends to \"eat\" closing tags in html strings within script elements\n        // see https://stackoverflow.com/questions/24575136/domdocument-removes-html-tags-in-javascript-string\n        $html = <<<END\n<!DOCTYPE html>\n<html lang=\"de\">\n<head>\n    <title>test</title>\n</head>\n<body>\n<div>\n    <script>\n        var html = '<b>Status</b><div>' + it_status_text + '</div>';\n    </script>\n</div>\n</body>\n</html>\nEND;\n        $hp = new HtmlPage($html);\n        $this->assertEquals($html . \"\\n\", $hp->save());\n    }\n}\n"
  },
  {
    "path": "Tests/phpunit_bootstrap.php",
    "content": "<?php\n// if we are checked out as a stand-alone project\n$loader = __DIR__ . '/../vendor/autoload.php';\n \n// if we are within the vendor directory of another project\nif (file_exists(__DIR__ . '/../../../../vendor/autoload.php')) {\n    $loader = __DIR__ . '/../../../../vendor/autoload.php';\n}\n \nif (!$loader = @include($loader)) {\n    echo <<<EOM\nYou must set up the project dependencies by running the following commands:\n \n    curl -s http://getcomposer.org/installer | php\n    php composer.phar install\n \nEOM;\n \n    exit(1);\n}\n"
  },
  {
    "path": "Tests/utf8.html",
    "content": "<!DOCTYPE html>\n\n<html lang=\"de\">\n<head>\n<meta charset=\"utf-8\" />\n</head>\n<body>\n        <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>\n        <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>\n</body>\n</html>\n"
  },
  {
    "path": "UPGRADE.md",
    "content": "Upgrade from 2.x to 3.0\n-----------------------\n\nRelease 3.x is compatible only with Symfony 6, while older releases are compatible with Symfony up to 5.4.\nOtherwise 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.\n\n\nUpgrade from 1.x to 2.0\n------------------------\n\nSeveral changes have been made to the public API in 2.0 in order to keep\ncompatibility with Symfony 4.3:\n\n- `HtmlPageCrawler::html()` is now just the parent `Crawler::html()` and acts as *getter* only.\n  Setting HTML content via `HtmlPageCrawler::html($html)` is *not possible* any more,\n  use `HtmlPageCrawler::setInnerHtml($html)` instead\n\n- `HtmlPageCrawler::text()` is now just the parent `Crawler::text()` and acts as *getter* only\n  that returns the text content from the *first* node only. For setting text content, use\n  `HtmlPageCrawler::setText($text)` instead.\n   \n- new method `HtmlPageCrawler::getCombinedText()` that returns the combined text from all nodes\n  (as jQuery's `text()` function does and previous versions of `HtmlPageCrawler::text()` did)\n\n- `HtmlPageCrawler::attr()` is now just the parent `Crawler::attr()` and acts as *getter* only.\n  For setting attributes use `HtmlPageCrawler::setAttribute($name, $value)` \n\n- removed method `HtmlPageCrawler::isDisconnected()`\n\n__To update your code, you have to:__\n\n- replace all calls to `$MyCrawlerInstance->html($html)` used as *setter* by `$MyCrawlerInstance->setInnerHtml($html)`\n- replace all calls to `$MyCrawlerInstance->attr($name, $value)` used as *setter* by `$MyCrawlerInstance->setAttribute($name, $value)`\n- replace all calls to `$MyCrawlerInstance->text($text)` used as *setter* by `$MyCrawlerInstance->setText($text)`\n- replace all calls to `$MyCrawlerInstance->text()` (i.e. every call to `text()` not preceded by `first()`) by `$MyCrawlerInstance->getCombinedText()`\n- replace all calls to `$MyCrawlerInstance->first()->text()` by `$MyCrawlerInstance->text()`\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\":\"wa72/htmlpagedom\",\n    \"description\":\"jQuery-inspired DOM manipulation extension for Symfony's Crawler\",\n    \"keywords\":[\"HTML\", \"DOM\", \"Crawler\"],\n    \"homepage\":\"http://github.com/wasinger/htmlpagedom\",\n    \"type\":\"library\",\n    \"license\":\"MIT\",\n    \"authors\":[\n        {\n            \"name\":\"Christoph Singer\",\n            \"email\":\"singer@webagentur72.de\",\n            \"homepage\":\"http://www.webagentur72.de\"\n        }\n    ],\n    \"require\":{\n        \"php\":\"^8.1\",\n        \"ext-dom\":\"*\",\n        \"ext-libxml\":\"*\",\n        \"symfony/polyfill-mbstring\": \"~1.0\",\n        \"symfony/dom-crawler\":\"^6.0 || ^7.0 || ^8.0\",\n        \"symfony/css-selector\":\"^6.0 || ^7.0 || ^8.0\"\n    },\n    \"require-dev\": {\n        \"phpunit/phpunit\": \"^9\",\n        \"wa72/html-pretty-min\": \"~0.1\",\n        \"mikey179/vfsstream\": \"^1.6.10\",\n        \"scrutinizer/ocular\": \"^1.9\",\n        \"clean/phpdoc-md\": \"^0.19.3\"\n    },\n    \"suggest\": {\n        \"wa72/html-pretty-min\": \"Minify or indent HTML documents\"\n    },\n    \"autoload\":{\n        \"psr-4\":{\n            \"Wa72\\\\HtmlPageDom\\\\\":\"src/\"\n        }\n    },\n    \"extra\": {\n        \"branch-alias\": {\n            \"dev-master\": \"3.0-dev\"\n        }\n    }\n}\n"
  },
  {
    "path": "doc/HtmlPage.md",
    "content": "# Wa72\\HtmlPageDom\\HtmlPage  \n\nThis class represents a complete HTML document.\n\nIt offers convenience functions for getting and setting elements of the document\nsuch as setTitle(), getTitle(), setMeta($name, $value), getBody().\n\nIt uses HtmlPageCrawler to navigate and manipulate the DOM tree.  \n\n## Implements:\nStringable\n\n\n\n## Methods\n\n| Name | Description |\n|------|-------------|\n|[__clone](#htmlpage__clone)||\n|[__construct](#htmlpage__construct)||\n|[__toString](#htmlpage__tostring)||\n|[filter](#htmlpagefilter)|Filter nodes by using a CSS selector|\n|[filterXPath](#htmlpagefilterxpath)|Filter nodes by XPath expression|\n|[getBaseHref](#htmlpagegetbasehref)|Get the href attribute from the base tag, null if not present in document|\n|[getBody](#htmlpagegetbody)|Get the document's body wrapped in a HtmlPageCrawler instance|\n|[getBodyNode](#htmlpagegetbodynode)|Get the document's body as DOMElement|\n|[getCrawler](#htmlpagegetcrawler)|Get a HtmlPageCrawler object containing the root node of the HTML document|\n|[getDOMDocument](#htmlpagegetdomdocument)|Get a DOMDocument object for the HTML document|\n|[getElementById](#htmlpagegetelementbyid)|Get an element in the document by it's id attribute|\n|[getHead](#htmlpagegethead)|Get the document's HEAD section wrapped in a HtmlPageCrawler instance|\n|[getHeadNode](#htmlpagegetheadnode)|Get the document's HEAD section as DOMElement|\n|[getMeta](#htmlpagegetmeta)|Get the content attribute of a meta tag with the specified name attribute|\n|[getTitle](#htmlpagegettitle)|Get the page title of the HTML document|\n|[indent](#htmlpageindent)|indent the HTML document|\n|[minify](#htmlpageminify)|minify the HTML document|\n|[removeMeta](#htmlpageremovemeta)|Remove all meta tags with the specified name attribute|\n|[save](#htmlpagesave)|Save this document to a HTML file or return HTML code as string|\n|[setBaseHref](#htmlpagesetbasehref)|Set the base tag with href attribute set to parameter $url|\n|[setHtmlById](#htmlpagesethtmlbyid)|Sets innerHTML content of an element specified by elementId|\n|[setMeta](#htmlpagesetmeta)|Set a META tag with specified 'name' and 'content' attributes|\n|[setTitle](#htmlpagesettitle)|Sets the page title of the HTML document|\n|[trimNewlines](#htmlpagetrimnewlines)|remove newlines from string and minimize whitespace (multiple whitespace characters replaced by one space)|\n\n\n\n\n### HtmlPage::__clone  \n\n**Description**\n\n```php\n __clone (void)\n```\n\n \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`void`\n\n\n<hr />\n\n\n### HtmlPage::__construct  \n\n**Description**\n\n```php\n __construct (void)\n```\n\n \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`void`\n\n\n<hr />\n\n\n### HtmlPage::__toString  \n\n**Description**\n\n```php\n __toString (void)\n```\n\n \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`void`\n\n\n<hr />\n\n\n### HtmlPage::filter  \n\n**Description**\n\n```php\npublic filter (string $selector)\n```\n\nFilter nodes by using a CSS selector \n\n \n\n**Parameters**\n\n* `(string) $selector`\n: CSS selector  \n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n\n\n\n<hr />\n\n\n### HtmlPage::filterXPath  \n\n**Description**\n\n```php\npublic filterXPath (string $xpath)\n```\n\nFilter nodes by XPath expression \n\n \n\n**Parameters**\n\n* `(string) $xpath`\n: XPath expression  \n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n\n\n\n<hr />\n\n\n### HtmlPage::getBaseHref  \n\n**Description**\n\n```php\npublic getBaseHref (void)\n```\n\nGet the href attribute from the base tag, null if not present in document \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`null|string`\n\n\n\n\n<hr />\n\n\n### HtmlPage::getBody  \n\n**Description**\n\n```php\npublic getBody (void)\n```\n\nGet the document's body wrapped in a HtmlPageCrawler instance \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n\n\n\n<hr />\n\n\n### HtmlPage::getBodyNode  \n\n**Description**\n\n```php\npublic getBodyNode (void)\n```\n\nGet the document's body as DOMElement \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`\\DOMElement`\n\n\n\n\n<hr />\n\n\n### HtmlPage::getCrawler  \n\n**Description**\n\n```php\npublic getCrawler (void)\n```\n\nGet a HtmlPageCrawler object containing the root node of the HTML document \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n\n\n\n<hr />\n\n\n### HtmlPage::getDOMDocument  \n\n**Description**\n\n```php\npublic getDOMDocument (void)\n```\n\nGet a DOMDocument object for the HTML document \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`\\DOMDocument`\n\n\n\n\n<hr />\n\n\n### HtmlPage::getElementById  \n\n**Description**\n\n```php\npublic getElementById (string $id)\n```\n\nGet an element in the document by it's id attribute \n\n \n\n**Parameters**\n\n* `(string) $id`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n\n\n\n<hr />\n\n\n### HtmlPage::getHead  \n\n**Description**\n\n```php\npublic getHead (void)\n```\n\nGet the document's HEAD section wrapped in a HtmlPageCrawler instance \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n\n\n\n<hr />\n\n\n### HtmlPage::getHeadNode  \n\n**Description**\n\n```php\npublic getHeadNode (void)\n```\n\nGet the document's HEAD section as DOMElement \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`\\DOMElement`\n\n\n\n\n<hr />\n\n\n### HtmlPage::getMeta  \n\n**Description**\n\n```php\npublic getMeta (string $name)\n```\n\nGet the content attribute of a meta tag with the specified name attribute \n\n \n\n**Parameters**\n\n* `(string) $name`\n\n**Return Values**\n\n`null|string`\n\n\n\n\n<hr />\n\n\n### HtmlPage::getTitle  \n\n**Description**\n\n```php\npublic getTitle (void)\n```\n\nGet the page title of the HTML document \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`null|string`\n\n\n\n\n<hr />\n\n\n### HtmlPage::indent  \n\n**Description**\n\n```php\npublic indent (array $options)\n```\n\nindent the HTML document \n\n \n\n**Parameters**\n\n* `(array) $options`\n: Options passed to PrettyMin::__construct()  \n\n**Return Values**\n\n`\\HtmlPage`\n\n\n\n\n**Throws Exceptions**\n\n\n`\\Exception`\n\n\n<hr />\n\n\n### HtmlPage::minify  \n\n**Description**\n\n```php\npublic minify (array $options)\n```\n\nminify the HTML document \n\n \n\n**Parameters**\n\n* `(array) $options`\n: Options passed to PrettyMin::__construct()  \n\n**Return Values**\n\n`\\HtmlPage`\n\n\n\n\n**Throws Exceptions**\n\n\n`\\Exception`\n\n\n<hr />\n\n\n### HtmlPage::removeMeta  \n\n**Description**\n\n```php\npublic removeMeta (string $name)\n```\n\nRemove all meta tags with the specified name attribute \n\n \n\n**Parameters**\n\n* `(string) $name`\n\n**Return Values**\n\n`void`\n\n\n<hr />\n\n\n### HtmlPage::save  \n\n**Description**\n\n```php\npublic save (string $filename)\n```\n\nSave this document to a HTML file or return HTML code as string \n\n \n\n**Parameters**\n\n* `(string) $filename`\n: If provided, output will be saved to this file, otherwise returned  \n\n**Return Values**\n\n`string|void`\n\n\n\n\n<hr />\n\n\n### HtmlPage::setBaseHref  \n\n**Description**\n\n```php\npublic setBaseHref (string $url)\n```\n\nSet the base tag with href attribute set to parameter $url \n\n \n\n**Parameters**\n\n* `(string) $url`\n\n**Return Values**\n\n`void`\n\n\n<hr />\n\n\n### HtmlPage::setHtmlById  \n\n**Description**\n\n```php\npublic setHtmlById (string $elementId, string $html)\n```\n\nSets innerHTML content of an element specified by elementId \n\n \n\n**Parameters**\n\n* `(string) $elementId`\n* `(string) $html`\n\n**Return Values**\n\n`void`\n\n\n<hr />\n\n\n### HtmlPage::setMeta  \n\n**Description**\n\n```php\npublic setMeta ( $name,  $content)\n```\n\nSet a META tag with specified 'name' and 'content' attributes \n\n \n\n**Parameters**\n\n* `() $name`\n* `() $content`\n\n**Return Values**\n\n`void`\n\n\n<hr />\n\n\n### HtmlPage::setTitle  \n\n**Description**\n\n```php\npublic setTitle (string $title)\n```\n\nSets the page title of the HTML document \n\n \n\n**Parameters**\n\n* `(string) $title`\n\n**Return Values**\n\n`void`\n\n\n<hr />\n\n\n### HtmlPage::trimNewlines  \n\n**Description**\n\n```php\npublic static trimNewlines (string $string)\n```\n\nremove newlines from string and minimize whitespace (multiple whitespace characters replaced by one space) \n\nuseful for cleaning up text retrieved by HtmlPageCrawler::text() (nodeValue of a DOMNode) \n\n**Parameters**\n\n* `(string) $string`\n\n**Return Values**\n\n`string`\n\n\n\n\n<hr />\n\n"
  },
  {
    "path": "doc/HtmlPageCrawler.md",
    "content": "# Wa72\\HtmlPageDom\\HtmlPageCrawler  \n\nExtends \\Symfony\\Component\\DomCrawler\\Crawler by adding tree manipulation functions\nfor HTML documents inspired by jQuery such as setInnerHtml(), css(), append(), prepend(), before(),\naddClass(), removeClass()\n\n## Implements:\nCountable, IteratorAggregate, Traversable, Stringable\n\n## Extend:\n\nSymfony\\Component\\DomCrawler\\Crawler\n\n## Methods\n\n| Name | Description |\n|------|-------------|\n|[__clone](#htmlpagecrawler__clone)||\n|[__get](#htmlpagecrawler__get)||\n|[__toString](#htmlpagecrawler__tostring)||\n|[addClass](#htmlpagecrawleraddclass)|Adds the specified class(es) to each element in the set of matched elements.|\n|[addHtmlFragment](#htmlpagecrawleraddhtmlfragment)||\n|[after](#htmlpagecrawlerafter)|Insert content, specified by the parameter, after each element in the set of matched elements.|\n|[append](#htmlpagecrawlerappend)|Insert HTML content as child nodes of each element after existing children|\n|[appendTo](#htmlpagecrawlerappendto)|Insert every element in the set of matched elements to the end of the target.|\n|[before](#htmlpagecrawlerbefore)|Insert content, specified by the parameter, before each element in the set of matched elements.|\n|[create](#htmlpagecrawlercreate)|Get an HtmlPageCrawler object from a HTML string, DOMNode, DOMNodeList or HtmlPageCrawler|\n|[css](#htmlpagecrawlercss)|Get one CSS style property of the first element or set it for all elements in the list|\n|[getAttribute](#htmlpagecrawlergetattribute)|Returns the attribute value of the first node of the list.|\n|[getCombinedText](#htmlpagecrawlergetcombinedtext)|Get the combined text contents of each element in the set of matched elements, including their descendants.|\n|[getDOMDocument](#htmlpagecrawlergetdomdocument)|get ownerDocument of the first element|\n|[getInnerHtml](#htmlpagecrawlergetinnerhtml)|Alias for Crawler::html() for naming consistency with setInnerHtml()|\n|[getStyle](#htmlpagecrawlergetstyle)|get one CSS style property of the first element|\n|[hasClass](#htmlpagecrawlerhasclass)|Determine whether any of the matched elements are assigned the given class.|\n|[insertAfter](#htmlpagecrawlerinsertafter)|Insert every element in the set of matched elements after the target.|\n|[insertBefore](#htmlpagecrawlerinsertbefore)|Insert every element in the set of matched elements before the target.|\n|[isHtmlDocument](#htmlpagecrawlerishtmldocument)|checks whether the first node contains a complete html document (as opposed to a document fragment)|\n|[makeClone](#htmlpagecrawlermakeclone)|Create a deep copy of the set of matched elements.|\n|[makeEmpty](#htmlpagecrawlermakeempty)|Removes all child nodes and text from all nodes in set|\n|[prepend](#htmlpagecrawlerprepend)|Insert content, specified by the parameter, to the beginning of each element in the set of matched elements.|\n|[prependTo](#htmlpagecrawlerprependto)|Insert every element in the set of matched elements to the beginning of the target.|\n|[remove](#htmlpagecrawlerremove)|Remove the set of matched elements from the DOM.|\n|[removeAttr](#htmlpagecrawlerremoveattr)|Remove an attribute from each element in the set of matched elements.|\n|[removeAttribute](#htmlpagecrawlerremoveattribute)|Remove an attribute from each element in the set of matched elements.|\n|[removeClass](#htmlpagecrawlerremoveclass)|Remove a class from each element in the list|\n|[replaceAll](#htmlpagecrawlerreplaceall)|Replace each target element with the set of matched elements.|\n|[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.|\n|[saveHTML](#htmlpagecrawlersavehtml)|Get the HTML code fragment of all elements and their contents.|\n|[setAttribute](#htmlpagecrawlersetattribute)|Sets an attribute on each element|\n|[setInnerHtml](#htmlpagecrawlersetinnerhtml)|Set the HTML contents of each element|\n|[setStyle](#htmlpagecrawlersetstyle)|set one CSS style property for all elements in the list|\n|[setText](#htmlpagecrawlersettext)|Set the text contents of the matched elements.|\n|[toggleClass](#htmlpagecrawlertoggleclass)|Add or remove one or more classes from each element in the set of matched elements, depending the class’s presence.|\n|[unwrap](#htmlpagecrawlerunwrap)|Remove the parents of the set of matched elements from the DOM, leaving the matched elements in their place.|\n|[unwrapInner](#htmlpagecrawlerunwrapinner)|Remove the matched elements, but promote the children to take their place.|\n|[wrap](#htmlpagecrawlerwrap)|Wrap an HTML structure around each element in the set of matched elements|\n|[wrapAll](#htmlpagecrawlerwrapall)|Wrap an HTML structure around all elements in the set of matched elements.|\n|[wrapInner](#htmlpagecrawlerwrapinner)|Wrap an HTML structure around the content of each element in the set of matched elements.|\n\n## Inherited methods\n\n| Name | Description |\n|------|-------------|\n| [__construct](https://secure.php.net/manual/en/symfony\\component\\domcrawler\\crawler.__construct.php) | - |\n|add|Adds a node to the current list of nodes.|\n|addContent|Adds HTML/XML content.|\n|addDocument|Adds a \\DOMDocument to the list of nodes.|\n|addHtmlContent|Adds an HTML content to the list of nodes.|\n|addNode|Adds a \\DOMNode instance to the list of nodes.|\n|addNodeList|Adds a \\DOMNodeList to the list of nodes.|\n|addNodes|Adds an array of \\DOMNode instances to the list of nodes.|\n|addXmlContent|Adds an XML content to the list of nodes.|\n|ancestors|Returns the ancestors of the current selection.|\n|attr|Returns the attribute value of the first node of the list.|\n|children|Returns the children nodes of the current selection.|\n|clear|Removes all the nodes.|\n|closest|Return first parents (heading toward the document root) of the Element that matches the provided selector.|\n| [count](https://secure.php.net/manual/en/symfony\\component\\domcrawler\\crawler.count.php) | - |\n|each|Calls an anonymous function on each node of the list.|\n|eq|Returns a node given its position in the node list.|\n|evaluate|Evaluates an XPath expression.|\n|extract|Extracts information from the list of nodes.|\n|filter|Filters the list of nodes with a CSS selector.|\n|filterXPath|Filters the list of nodes with an XPath expression.|\n|first|Returns the first node of the current selection.|\n|form|Returns a Form object for the first node in the list.|\n|getBaseHref|Returns base href.|\n| [getIterator](https://secure.php.net/manual/en/symfony\\component\\domcrawler\\crawler.getiterator.php) | - |\n| [getNode](https://secure.php.net/manual/en/symfony\\component\\domcrawler\\crawler.getnode.php) | - |\n|getUri|Returns the current URI.|\n|html|Returns the first node of the list as HTML.|\n|image|Returns an Image object for the first node in the list.|\n|images|Returns an array of Image objects for the nodes in the list.|\n|innerText|Returns only the inner text that is the direct descendent of the current node, excluding any child nodes.|\n|last|Returns the last node of the current selection.|\n|link|Returns a Link object for the first node in the list.|\n|links|Returns an array of Link objects for the nodes in the list.|\n| [matches](https://secure.php.net/manual/en/symfony\\component\\domcrawler\\crawler.matches.php) | - |\n|nextAll|Returns the next siblings nodes of the current selection.|\n|nodeName|Returns the node name of the first node of the list.|\n| [outerHtml](https://secure.php.net/manual/en/symfony\\component\\domcrawler\\crawler.outerhtml.php) | - |\n|previousAll|Returns the previous sibling nodes of the current selection.|\n|reduce|Reduces the list of nodes by calling an anonymous function.|\n| [registerNamespace](https://secure.php.net/manual/en/symfony\\component\\domcrawler\\crawler.registernamespace.php) | - |\n|selectButton|Selects a button by name or alt value for images.|\n|selectImage|Selects images by alt value.|\n|selectLink|Selects links by name or alt value for clickable images.|\n|setDefaultNamespacePrefix|Overloads a default namespace prefix to be used with XPath and CSS expressions.|\n|siblings|Returns the siblings nodes of the current selection.|\n|slice|Slices the list of nodes by $offset and $length.|\n|text|Returns the text of the first node of the list.|\n|xpathLiteral|Converts string for XPath expressions.|\n\n\n\n### HtmlPageCrawler::__clone  \n\n**Description**\n\n```php\n __clone (void)\n```\n\n \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`void`\n\n\n<hr />\n\n\n### HtmlPageCrawler::__get  \n\n**Description**\n\n```php\n __get (void)\n```\n\n \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`void`\n\n\n<hr />\n\n\n### HtmlPageCrawler::__toString  \n\n**Description**\n\n```php\n __toString (void)\n```\n\n \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`void`\n\n\n<hr />\n\n\n### HtmlPageCrawler::addClass  \n\n**Description**\n\n```php\npublic addClass (string $name)\n```\n\nAdds the specified class(es) to each element in the set of matched elements. \n\n \n\n**Parameters**\n\n* `(string) $name`\n: One or more space-separated classes to be added to the class attribute of each matched element.  \n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n\n### HtmlPageCrawler::addHtmlFragment  \n\n**Description**\n\n```php\n addHtmlFragment (void)\n```\n\n \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`void`\n\n\n<hr />\n\n\n### HtmlPageCrawler::after  \n\n**Description**\n\n```php\npublic after (string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content)\n```\n\nInsert content, specified by the parameter, after each element in the set of matched elements. \n\n \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList) $content`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n\n### HtmlPageCrawler::append  \n\n**Description**\n\n```php\npublic append (string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content)\n```\n\nInsert HTML content as child nodes of each element after existing children \n\n \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList) $content`\n: HTML code fragment or DOMNode to append  \n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n\n### HtmlPageCrawler::appendTo  \n\n**Description**\n\n```php\npublic appendTo (string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList $element)\n```\n\nInsert every element in the set of matched elements to the end of the target. \n\n \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList) $element`\n\n**Return Values**\n\n`\\Wa72\\HtmlPageDom\\HtmlPageCrawler`\n\n> A new Crawler object containing all elements appended to the target elements\n\n\n<hr />\n\n\n### HtmlPageCrawler::before  \n\n**Description**\n\n```php\npublic before (string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content)\n```\n\nInsert content, specified by the parameter, before each element in the set of matched elements. \n\n \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList) $content`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n\n### HtmlPageCrawler::create  \n\n**Description**\n\n```php\npublic static create (string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList|array $content)\n```\n\nGet an HtmlPageCrawler object from a HTML string, DOMNode, DOMNodeList or HtmlPageCrawler \n\nThis is the equivalent to jQuery's $() function when used for wrapping DOMNodes or creating DOMElements from HTML code. \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList|array) $content`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n\n\n\n<hr />\n\n\n### HtmlPageCrawler::css  \n\n**Description**\n\n```php\npublic css (string $key, null|string $value)\n```\n\nGet one CSS style property of the first element or set it for all elements in the list \n\nFunction is here for compatibility with jQuery; it is the same as getStyle() and setStyle() \n\n**Parameters**\n\n* `(string) $key`\n: The name of the style property  \n* `(null|string) $value`\n: The CSS value to set, or NULL to get the current value  \n\n**Return Values**\n\n`\\HtmlPageCrawler|string`\n\n> If no param is provided, returns the CSS styles of the first element\n\n\n<hr />\n\n\n### HtmlPageCrawler::getAttribute  \n\n**Description**\n\n```php\npublic getAttribute (string $name)\n```\n\nReturns the attribute value of the first node of the list. \n\nThis is just an alias for attr() for naming consistency with setAttribute() \n\n**Parameters**\n\n* `(string) $name`\n: The attribute name  \n\n**Return Values**\n\n`string|null`\n\n> The attribute value or null if the attribute does not exist\n\n\n**Throws Exceptions**\n\n\n`\\InvalidArgumentException`\n> When current node is empty\n\n<hr />\n\n\n### HtmlPageCrawler::getCombinedText  \n\n**Description**\n\n```php\npublic getCombinedText (void)\n```\n\nGet the combined text contents of each element in the set of matched elements, including their descendants. \n\nThis is what the jQuery text() function does, contrary to the Crawler::text() method that returns only  \nthe text of the first node. \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`string`\n\n\n\n\n<hr />\n\n\n### HtmlPageCrawler::getDOMDocument  \n\n**Description**\n\n```php\npublic getDOMDocument (void)\n```\n\nget ownerDocument of the first element \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`\\DOMDocument|null`\n\n\n\n\n<hr />\n\n\n### HtmlPageCrawler::getInnerHtml  \n\n**Description**\n\n```php\npublic getInnerHtml (void)\n```\n\nAlias for Crawler::html() for naming consistency with setInnerHtml() \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`string`\n\n\n\n\n<hr />\n\n\n### HtmlPageCrawler::getStyle  \n\n**Description**\n\n```php\npublic getStyle (string $key)\n```\n\nget one CSS style property of the first element \n\n \n\n**Parameters**\n\n* `(string) $key`\n: name of the property  \n\n**Return Values**\n\n`string|null`\n\n> value of the property\n\n\n<hr />\n\n\n### HtmlPageCrawler::hasClass  \n\n**Description**\n\n```php\npublic hasClass (string $name)\n```\n\nDetermine whether any of the matched elements are assigned the given class. \n\n \n\n**Parameters**\n\n* `(string) $name`\n\n**Return Values**\n\n`bool`\n\n\n\n\n<hr />\n\n\n### HtmlPageCrawler::insertAfter  \n\n**Description**\n\n```php\npublic insertAfter (string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList $element)\n```\n\nInsert every element in the set of matched elements after the target. \n\n \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList) $element`\n\n**Return Values**\n\n`\\Wa72\\HtmlPageDom\\HtmlPageCrawler`\n\n> A new Crawler object containing all elements appended to the target elements\n\n\n<hr />\n\n\n### HtmlPageCrawler::insertBefore  \n\n**Description**\n\n```php\npublic insertBefore (string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList $element)\n```\n\nInsert every element in the set of matched elements before the target. \n\n \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList) $element`\n\n**Return Values**\n\n`\\Wa72\\HtmlPageDom\\HtmlPageCrawler`\n\n> A new Crawler object containing all elements appended to the target elements\n\n\n<hr />\n\n\n### HtmlPageCrawler::isHtmlDocument  \n\n**Description**\n\n```php\npublic isHtmlDocument (void)\n```\n\nchecks whether the first node contains a complete html document (as opposed to a document fragment) \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`bool`\n\n\n\n\n<hr />\n\n\n### HtmlPageCrawler::makeClone  \n\n**Description**\n\n```php\npublic makeClone (void)\n```\n\nCreate a deep copy of the set of matched elements. \n\nEquivalent to clone() in jQuery (clone is not a valid PHP function name) \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n\n\n\n<hr />\n\n\n### HtmlPageCrawler::makeEmpty  \n\n**Description**\n\n```php\npublic makeEmpty (void)\n```\n\nRemoves all child nodes and text from all nodes in set \n\nEquivalent to jQuery's empty() function which is not a valid function name in PHP \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n> $this\n\n\n<hr />\n\n\n### HtmlPageCrawler::prepend  \n\n**Description**\n\n```php\npublic prepend (string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content)\n```\n\nInsert content, specified by the parameter, to the beginning of each element in the set of matched elements. \n\n \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList) $content`\n: HTML code fragment  \n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n\n### HtmlPageCrawler::prependTo  \n\n**Description**\n\n```php\npublic prependTo (string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList $element)\n```\n\nInsert every element in the set of matched elements to the beginning of the target. \n\n \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList) $element`\n\n**Return Values**\n\n`\\Wa72\\HtmlPageDom\\HtmlPageCrawler`\n\n> A new Crawler object containing all elements prepended to the target elements\n\n\n<hr />\n\n\n### HtmlPageCrawler::remove  \n\n**Description**\n\n```php\npublic remove (void)\n```\n\nRemove the set of matched elements from the DOM. \n\n(as opposed to Crawler::clear() which detaches the nodes only from Crawler  \nbut leaves them in the DOM) \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`void`\n\n\n<hr />\n\n\n### HtmlPageCrawler::removeAttr  \n\n**Description**\n\n```php\npublic removeAttr (string $name)\n```\n\nRemove an attribute from each element in the set of matched elements. \n\nAlias for removeAttribute for compatibility with jQuery \n\n**Parameters**\n\n* `(string) $name`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n\n\n\n<hr />\n\n\n### HtmlPageCrawler::removeAttribute  \n\n**Description**\n\n```php\npublic removeAttribute (string $name)\n```\n\nRemove an attribute from each element in the set of matched elements. \n\n \n\n**Parameters**\n\n* `(string) $name`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n\n\n\n<hr />\n\n\n### HtmlPageCrawler::removeClass  \n\n**Description**\n\n```php\npublic removeClass (string $name)\n```\n\nRemove a class from each element in the list \n\n \n\n**Parameters**\n\n* `(string) $name`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n\n### HtmlPageCrawler::replaceAll  \n\n**Description**\n\n```php\npublic replaceAll (string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList $element)\n```\n\nReplace each target element with the set of matched elements. \n\n \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList) $element`\n\n**Return Values**\n\n`\\Wa72\\HtmlPageDom\\HtmlPageCrawler`\n\n> A new Crawler object containing all elements appended to the target elements\n\n\n<hr />\n\n\n### HtmlPageCrawler::replaceWith  \n\n**Description**\n\n```php\npublic replaceWith (string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content)\n```\n\nReplace each element in the set of matched elements with the provided new content and return the set of elements that was removed. \n\n \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList) $content`\n\n**Return Values**\n\n`\\Wa72\\HtmlPageDom\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n\n### HtmlPageCrawler::saveHTML  \n\n**Description**\n\n```php\npublic saveHTML (void)\n```\n\nGet the HTML code fragment of all elements and their contents. \n\nIf the first node contains a complete HTML document return only  \nthe full code of this document. \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`string`\n\n> HTML code (fragment)\n\n\n<hr />\n\n\n### HtmlPageCrawler::setAttribute  \n\n**Description**\n\n```php\npublic setAttribute (string $name, string $value)\n```\n\nSets an attribute on each element \n\n \n\n**Parameters**\n\n* `(string) $name`\n* `(string) $value`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n\n### HtmlPageCrawler::setInnerHtml  \n\n**Description**\n\n```php\npublic setInnerHtml (string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content)\n```\n\nSet the HTML contents of each element \n\n \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList) $content`\n: HTML code fragment  \n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n\n### HtmlPageCrawler::setStyle  \n\n**Description**\n\n```php\npublic setStyle (string $key, string $value)\n```\n\nset one CSS style property for all elements in the list \n\n \n\n**Parameters**\n\n* `(string) $key`\n: name of the property  \n* `(string) $value`\n: value of the property  \n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n\n### HtmlPageCrawler::setText  \n\n**Description**\n\n```php\npublic setText (string $text)\n```\n\nSet the text contents of the matched elements. \n\n \n\n**Parameters**\n\n* `(string) $text`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n\n\n\n<hr />\n\n\n### HtmlPageCrawler::toggleClass  \n\n**Description**\n\n```php\npublic toggleClass (string $classname)\n```\n\nAdd or remove one or more classes from each element in the set of matched elements, depending the class’s presence. \n\n \n\n**Parameters**\n\n* `(string) $classname`\n: One or more classnames separated by spaces  \n\n**Return Values**\n\n`\\Wa72\\HtmlPageDom\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n\n### HtmlPageCrawler::unwrap  \n\n**Description**\n\n```php\npublic unwrap (void)\n```\n\nRemove the parents of the set of matched elements from the DOM, leaving the matched elements in their place. \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`\\Wa72\\HtmlPageDom\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n\n### HtmlPageCrawler::unwrapInner  \n\n**Description**\n\n```php\npublic unwrapInner (void)\n```\n\nRemove the matched elements, but promote the children to take their place. \n\n \n\n**Parameters**\n\n`This function has no parameters.`\n\n**Return Values**\n\n`\\Wa72\\HtmlPageDom\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n\n### HtmlPageCrawler::wrap  \n\n**Description**\n\n```php\npublic wrap (string|\\HtmlPageCrawler|\\DOMNode $wrappingElement)\n```\n\nWrap an HTML structure around each element in the set of matched elements \n\nThe HTML structure must contain only one root node, e.g.:  \nWorks: <div><div></div></div>  \nDoes not work: <div></div><div></div> \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode) $wrappingElement`\n\n**Return Values**\n\n`\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n\n### HtmlPageCrawler::wrapAll  \n\n**Description**\n\n```php\npublic wrapAll (string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content)\n```\n\nWrap an HTML structure around all elements in the set of matched elements. \n\n \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList) $content`\n\n**Return Values**\n\n`\\Wa72\\HtmlPageDom\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n**Throws Exceptions**\n\n\n`\\LogicException`\n\n\n<hr />\n\n\n### HtmlPageCrawler::wrapInner  \n\n**Description**\n\n```php\npublic wrapInner (string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content)\n```\n\nWrap an HTML structure around the content of each element in the set of matched elements. \n\n \n\n**Parameters**\n\n* `(string|\\HtmlPageCrawler|\\DOMNode|\\DOMNodeList) $content`\n\n**Return Values**\n\n`\\Wa72\\HtmlPageDom\\HtmlPageCrawler`\n\n> $this for chaining\n\n\n<hr />\n\n"
  },
  {
    "path": "doc/README.md",
    "content": "# Wa72\\HtmlPageDom\n\n* [HtmlPage](HtmlPage.md) \n* [HtmlPageCrawler](HtmlPageCrawler.md) \n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<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\">\n  <coverage>\n    <include>\n      <directory suffix=\".php\">./src/</directory>\n    </include>\n  </coverage>\n  <testsuites>\n    <testsuite name=\"HtmlPageDom Test Suite\">\n      <directory suffix=\"Test.php\">./Tests/</directory>\n    </testsuite>\n  </testsuites>\n</phpunit>\n"
  },
  {
    "path": "src/Helpers.php",
    "content": "<?php\nnamespace Wa72\\HtmlPageDom;\n\n/**\n * Static helper functions for HtmlPageDom\n *\n * @package Wa72\\HtmlPageDom\n */\nclass Helpers {\n\n    /**\n     * remove newlines from string and minimize whitespace (multiple whitespace characters replaced by one space)\n     * useful for cleaning up text retrieved by HtmlPageCrawler::text() (nodeValue of a DOMNode)\n     *\n     * @param string $string\n     * @return string\n     */\n    public static function trimNewlines($string)\n    {\n        $string = str_replace(\"\\n\", ' ', $string);\n        $string = str_replace(\"\\r\", ' ', $string);\n        $string = preg_replace('/\\s+/', ' ', $string);\n        return trim($string);\n    }\n\n    /**\n     * Convert CSS string to array\n     *\n     * @param string $css list of CSS properties separated by ;\n     * @return array name=>value pairs of CSS properties\n     */\n    public static function cssStringToArray($css)\n    {\n        $statements = explode(';', preg_replace('/\\s+/s', ' ', $css));\n        $styles = array();\n        foreach ($statements as $statement) {\n            $statement = trim($statement);\n            if ('' === $statement) {\n                continue;\n            }\n            $p = strpos($statement, ':');\n            if ($p <= 0) {\n                continue;\n            } // invalid statement, just ignore it\n            $key = trim(substr($statement, 0, $p));\n            $value = trim(substr($statement, $p + 1));\n            $styles[$key] = $value;\n        }\n        return $styles;\n    }\n\n    /**\n     * Convert CSS name->value array to string\n     *\n     * @param array $array name=>value pairs of CSS properties\n     * @return string list of CSS properties separated by ;\n     */\n    public static function cssArrayToString($array)\n    {\n        $styles = '';\n        foreach ($array as $key => $value) {\n            $styles .= $key . ': ' . $value . ';';\n        }\n        return $styles;\n    }\n\n    /**\n     * Helper function for getting a body element\n     * from an HTML fragment\n     *\n     * @param string $html A fragment of HTML code\n     * @param string $charset\n     * @return \\DOMNode The body node containing child nodes created from the HTML fragment\n     */\n    public static function getBodyNodeFromHtmlFragment($html, $charset = 'UTF-8')\n    {\n\n        $html = '<html><body>' . $html . '</body></html>';\n        $d = self::loadHtml($html, $charset);\n        return $d->getElementsByTagName('body')->item(0);\n    }\n\n    public static function loadHtml(string $html, $charset = 'UTF-8'): \\DOMDocument\n    {\n        return self::parseXhtml($html, $charset);\n    }\n    /**\n     * Function originally taken from Symfony\\Component\\DomCrawler\\Crawler\n     * (c) Fabien Potencier <fabien@symfony.com>\n     * License: MIT\n     */\n    private static function parseXhtml(string $htmlContent, string $charset = 'UTF-8'): \\DOMDocument\n    {\n        $htmlContent = self::convertToHtmlEntities($htmlContent, $charset);\n\n        $internalErrors = libxml_use_internal_errors(true);\n\n        $dom = new \\DOMDocument('1.0', $charset);\n        $dom->validateOnParse = true;\n\n        if ('' !== trim($htmlContent)) {\n            // PHP DOMDocument->loadHTML method tends to \"eat\" closing tags in html strings within script elements\n            // Option LIBXML_SCHEMA_CREATE seems to prevent this\n            // see https://stackoverflow.com/questions/24575136/domdocument-removes-html-tags-in-javascript-string\n            @$dom->loadHTML($htmlContent, \\LIBXML_SCHEMA_CREATE);\n        }\n\n        libxml_use_internal_errors($internalErrors);\n\n        return $dom;\n    }\n\n    /**\n     * Converts charset to HTML-entities to ensure valid parsing.\n     * Function taken from Symfony\\Component\\DomCrawler\\Crawler\n     * (c) Fabien Potencier <fabien@symfony.com>\n     * License: MIT\n     */\n    private static function convertToHtmlEntities(string $htmlContent, string $charset = 'UTF-8'): string\n    {\n        set_error_handler(function () { throw new \\Exception(); });\n\n        try {\n            return mb_encode_numericentity($htmlContent, [0x80, 0x10FFFF, 0, 0x1FFFFF], $charset);\n        } catch (\\Exception|\\ValueError) {\n            try {\n                $htmlContent = iconv($charset, 'UTF-8', $htmlContent);\n                $htmlContent = mb_encode_numericentity($htmlContent, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8');\n            } catch (\\Exception|\\ValueError) {\n            }\n            return $htmlContent;\n        } finally {\n            restore_error_handler();\n        }\n    }\n}\n"
  },
  {
    "path": "src/HtmlPage.php",
    "content": "<?php\nnamespace Wa72\\HtmlPageDom;\n\nuse Symfony\\Component\\CssSelector\\CssSelector;\nuse Wa72\\HtmlPrettymin\\PrettyMin;\n\n/**\n * This class represents a complete HTML document.\n *\n * It offers convenience functions for getting and setting elements of the document\n * such as setTitle(), getTitle(), setMeta($name, $value), getBody().\n *\n * It uses HtmlPageCrawler to navigate and manipulate the DOM tree.\n *\n * @author Christoph Singer\n * @license MIT\n */\nclass HtmlPage\n{\n    /**\n     *\n     * @var \\DOMDocument\n     */\n    protected $dom;\n\n    /**\n     * @var string\n     */\n    protected $charset;\n\n    /**\n     * @var string\n     */\n    protected $url;\n\n    /**\n     *\n     * @var HtmlPageCrawler\n     */\n    protected $crawler;\n\n    public function __construct($content = '', $url = '', $charset = 'UTF-8')\n    {\n        $this->charset = $charset;\n        $this->url = $url;\n        if ($content == '') {\n            $content = '<!DOCTYPE html><html><head><title></title></head><body></body></html>';\n        }\n        $this->dom = Helpers::loadHtml($content, $charset);\n        $this->crawler = new HtmlPageCrawler($this->dom);\n    }\n\n    /**\n     * Get a HtmlPageCrawler object containing the root node of the HTML document\n     *\n     * @return HtmlPageCrawler\n     */\n    public function getCrawler()\n    {\n        return $this->crawler;\n    }\n\n    /**\n     * Get a DOMDocument object for the HTML document\n     *\n     * @return \\DOMDocument\n     */\n    public function getDOMDocument()\n    {\n        return $this->dom;\n    }\n\n    /**\n     * Sets the page title of the HTML document\n     *\n     * @param string $title\n     */\n    public function setTitle($title)\n    {\n        $t = $this->dom->getElementsByTagName('title')->item(0);\n        if ($t == null) {\n            $t = $this->dom->createElement('title');\n            $this->getHeadNode()->appendChild($t);\n        }\n        $t->nodeValue = htmlspecialchars($title);\n    }\n\n    /**\n     * Get the page title of the HTML document\n     *\n     * @return null|string\n     */\n    public function getTitle()\n    {\n        $t = $this->dom->getElementsByTagName('title')->item(0);\n        if ($t == null) {\n            return null;\n        } else {\n            return $t->nodeValue;\n        }\n    }\n\n    /**\n     * Set a META tag with specified 'name' and 'content' attributes\n     *\n     * @TODO: add support for multiple meta tags with the same name but different languages\n     *\n     * @param $name\n     * @param $content\n     */\n    public function setMeta($name, $content)\n    {\n        $c = $this->filterXPath('descendant-or-self::meta[@name = \\'' . $name . '\\']');\n        if (count($c) == 0) {\n            $node = $this->dom->createElement('meta');\n            $node->setAttribute('name', $name);\n            $this->getHeadNode()->appendChild($node);\n            $c->addNode($node);\n        }\n        $c->setAttribute('content', $content);\n    }\n\n    /**\n     * Remove all meta tags with the specified name attribute\n     *\n     * @param string $name\n     */\n    public function removeMeta($name)\n    {\n        $meta = $this->filterXPath('descendant-or-self::meta[@name = \\'' . $name . '\\']');\n        $meta->remove();\n    }\n\n    /**\n     * Get the content attribute of a meta tag with the specified name attribute\n     *\n     * @param string $name\n     * @return null|string\n     */\n    public function getMeta($name)\n    {\n        $node = $this->filterXPath('descendant-or-self::meta[@name = \\'' . $name . '\\']')->getNode(0);\n        if ($node instanceof \\DOMElement) {\n            return $node->getAttribute('content');\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Set the base tag with href attribute set to parameter $url\n     *\n     * @param string $url\n     */\n    public function setBaseHref($url)\n    {\n        $node = $this->filterXPath('descendant-or-self::base')->getNode(0);\n        if ($node == null) {\n            $node = $this->dom->createElement('base');\n            $this->getHeadNode()->appendChild($node);\n        }\n        $node->setAttribute('href', $url);\n    }\n\n    /**\n     * Get the href attribute from the base tag, null if not present in document\n     *\n     * @return null|string\n     */\n    public function getBaseHref()\n    {\n        $node = $this->filterXPath('descendant-or-self::base')->getNode(0);\n        if ($node instanceof \\DOMElement) {\n            return $node->getAttribute('href');\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Sets innerHTML content of an element specified by elementId\n     *\n     * @param string $elementId\n     * @param string $html\n     */\n    public function setHtmlById($elementId, $html)\n    {\n        $this->getElementById($elementId)->setInnerHtml($html);\n    }\n\n    /**\n     * Get the document's HEAD section as DOMElement\n     *\n     * @return \\DOMElement\n     */\n    public function getHeadNode()\n    {\n        $head = $this->dom->getElementsByTagName('head')->item(0);\n        if ($head == null) {\n            $head = $this->dom->createElement('head');\n            $head = $this->dom->documentElement->insertBefore($head, $this->getBodyNode());\n        }\n        return $head;\n    }\n\n    /**\n     * Get the document's body as DOMElement\n     *\n     * @return \\DOMElement\n     */\n    public function getBodyNode()\n    {\n        $body = $this->dom->getElementsByTagName('body')->item(0);\n        if ($body == null) {\n            $body = $this->dom->createElement('body');\n            $body = $this->dom->documentElement->appendChild($body);\n        }\n        return $body;\n    }\n\n    /**\n     * Get the document's HEAD section wrapped in a HtmlPageCrawler instance\n     *\n     * @return HtmlPageCrawler\n     */\n    public function getHead()\n    {\n        return new HtmlPageCrawler($this->getHeadNode());\n    }\n\n    /**\n     * Get the document's body wrapped in a HtmlPageCrawler instance\n     *\n     * @return HtmlPageCrawler\n     */\n    public function getBody()\n    {\n        return new HtmlPageCrawler($this->getBodyNode());\n    }\n\n    public function __toString()\n    {\n        return $this->dom->saveHTML();\n    }\n\n    /**\n     * Save this document to a HTML file or return HTML code as string\n     *\n     * @param string $filename If provided, output will be saved to this file, otherwise returned\n     * @return string|void\n     */\n    public function save($filename = '')\n    {\n        if ($filename != '') {\n            file_put_contents($filename, (string) $this);\n            return;\n        } else {\n            return (string) $this;\n        }\n    }\n\n    /**\n     * Get an element in the document by it's id attribute\n     *\n     * @param string $id\n     * @return HtmlPageCrawler\n     */\n    public function getElementById($id)\n    {\n        return $this->filterXPath('descendant-or-self::*[@id = \\'' . $id . '\\']');\n    }\n\n    /**\n     * Filter nodes by using a CSS selector\n     *\n     * @param string $selector CSS selector\n     * @return HtmlPageCrawler\n     */\n    public function filter($selector)\n    {\n        //echo \"\\n\" . CssSelector::toXPath($selector) . \"\\n\";\n        return $this->crawler->filter($selector);\n    }\n\n    /**\n     * Filter nodes by XPath expression\n     *\n     * @param string $xpath XPath expression\n     * @return HtmlPageCrawler\n     */\n    public function filterXPath($xpath)\n    {\n        return $this->crawler->filterXPath($xpath);\n    }\n\n    /**\n     * remove newlines from string and minimize whitespace (multiple whitespace characters replaced by one space)\n     *\n     * useful for cleaning up text retrieved by HtmlPageCrawler::text() (nodeValue of a DOMNode)\n     *\n     * @param string $string\n     * @return string\n     */\n    public static function trimNewlines($string)\n    {\n        return Helpers::trimNewlines($string);\n    }\n\n    public function __clone()\n    {\n        $this->dom = $this->dom->cloneNode(true);\n        $this->crawler = new HtmlPageCrawler($this->dom);\n    }\n\n    /**\n     * minify the HTML document\n     *\n     * @param array $options Options passed to PrettyMin::__construct()\n     * @return HtmlPage\n     * @throws \\Exception\n     */\n    public function minify(array $options = array())\n    {\n        if (!class_exists('Wa72\\\\HtmlPrettymin\\\\PrettyMin')) {\n            throw new \\Exception('Function minify needs composer package wa72/html-pretty-min');\n        }\n        $pm = new PrettyMin($options);\n        $pm->load($this->dom)->minify();\n        return $this;\n    }\n\n    /**\n     * indent the HTML document\n     *\n     * @param array $options Options passed to PrettyMin::__construct()\n     * @return HtmlPage\n     * @throws \\Exception\n     */\n    public function indent(array $options = array())\n    {\n        if (!class_exists('Wa72\\\\HtmlPrettymin\\\\PrettyMin')) {\n            throw new \\Exception('Function indent needs composer package wa72/html-pretty-min');\n        }\n        $pm = new PrettyMin($options);\n        $pm->load($this->dom)->indent();\n        return $this;\n    }\n}\n"
  },
  {
    "path": "src/HtmlPageCrawler.php",
    "content": "<?php\nnamespace Wa72\\HtmlPageDom;\n\nuse Symfony\\Component\\DomCrawler\\Crawler;\n\n/**\n * Extends \\Symfony\\Component\\DomCrawler\\Crawler by adding tree manipulation functions\n * for HTML documents inspired by jQuery such as setInnerHtml(), css(), append(), prepend(), before(),\n * addClass(), removeClass()\n *\n * @author Christoph Singer\n * @license MIT\n *\n */\nclass HtmlPageCrawler extends Crawler\n{\n    /**\n     * the (internal) root element name used when importing html fragments\n     * */\n    const FRAGMENT_ROOT_TAGNAME = '_root';\n\n    /**\n     * Get an HtmlPageCrawler object from a HTML string, DOMNode, DOMNodeList or HtmlPageCrawler\n     *\n     * This is the equivalent to jQuery's $() function when used for wrapping DOMNodes or creating DOMElements from HTML code.\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode|\\DOMNodeList|array $content\n     * @return HtmlPageCrawler\n     * @api\n     */\n    public static function create($content)\n    {\n        if ($content instanceof HtmlPageCrawler) {\n            return $content;\n        } else {\n            return new HtmlPageCrawler($content);\n        }\n    }\n\n    /**\n     * Adds the specified class(es) to each element in the set of matched elements.\n     *\n     * @param string $name One or more space-separated classes to be added to the class attribute of each matched element.\n     * @return HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function addClass($name)\n    {\n        foreach ($this as $node) {\n            if ($node instanceof \\DOMElement) {\n                /** @var \\DOMElement $node */\n                $classes = preg_split('/\\s+/s', $node->getAttribute('class'));\n                $found = false;\n                $count = count($classes);\n                for ($i = 0; $i < $count; $i++) {\n                    if ($classes[$i] == $name) {\n                        $found = true;\n                    }\n                }\n                if (!$found) {\n                    $classes[] = $name;\n                    $node->setAttribute('class', trim(join(' ', $classes)));\n                }\n            }\n        }\n        return $this;\n    }\n\n    /**\n     * Insert content, specified by the parameter, after each element in the set of matched elements.\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content\n     * @return HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function after($content)\n    {\n        $content = self::create($content);\n        $newnodes = array();\n        foreach ($this as $i => $node) {\n            /** @var \\DOMNode $node */\n            $refnode = $node->nextSibling;\n            foreach ($content as $newnode) {\n                /** @var \\DOMNode $newnode */\n                $newnode = static::importNewnode($newnode, $node, $i);\n                if ($refnode === null) {\n                    $node->parentNode->appendChild($newnode);\n                } else {\n                    $node->parentNode->insertBefore($newnode, $refnode);\n                }\n                $newnodes[] = $newnode;\n            }\n        }\n        $content->clear();\n        $content->add($newnodes);\n        return $this;\n    }\n\n    /**\n     * Insert HTML content as child nodes of each element after existing children\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content HTML code fragment or DOMNode to append\n     * @return HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function append($content)\n    {\n        $content = self::create($content);\n        $newnodes = array();\n        foreach ($this as $i => $node) {\n            /** @var \\DOMNode $node */\n            foreach ($content as $newnode) {\n                /** @var \\DOMNode $newnode */\n                $newnode = static::importNewnode($newnode, $node, $i);\n                $node->appendChild($newnode);\n                $newnodes[] = $newnode;\n            }\n        }\n        $content->clear();\n        $content->add($newnodes);\n        return $this;\n    }\n\n    /**\n     * Insert every element in the set of matched elements to the end of the target.\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode|\\DOMNodeList $element\n     * @return \\Wa72\\HtmlPageDom\\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements\n     * @api\n     */\n    public function appendTo($element)\n    {\n        $e = self::create($element);\n        $newnodes = array();\n        foreach ($e as $i => $node) {\n            /** @var \\DOMNode $node */\n            foreach ($this as $newnode) {\n                /** @var \\DOMNode $newnode */\n                if ($node !== $newnode) {\n                    $newnode = static::importNewnode($newnode, $node, $i);\n                    $node->appendChild($newnode);\n                }\n                $newnodes[] = $newnode;\n            }\n        }\n        return self::create($newnodes);\n    }\n\n    /**\n     * Sets an attribute on each element\n     *\n     * @param string $name\n     * @param string $value\n     * @return HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function setAttribute($name, $value)\n    {\n        foreach ($this as $node) {\n            if ($node instanceof \\DOMElement) {\n                /** @var \\DOMElement $node */\n                $node->setAttribute($name, $value);\n            }\n        }\n        return $this;\n    }\n\n    /**\n     * Returns the attribute value of the first node of the list.\n     * This is just an alias for attr() for naming consistency with setAttribute()\n     *\n     * @param string $name The attribute name\n     * @return string|null The attribute value or null if the attribute does not exist\n     * @throws \\InvalidArgumentException When current node is empty\n     */\n    public function getAttribute($name)\n    {\n        return parent::attr($name);\n    }\n\n    /**\n     * Insert content, specified by the parameter, before each element in the set of matched elements.\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content\n     * @return HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function before($content)\n    {\n        $content = self::create($content);\n        $newnodes = array();\n        foreach ($this as $i => $node) {\n            /** @var \\DOMNode $node */\n            foreach ($content as $newnode) {\n                /** @var \\DOMNode $newnode */\n                if ($node !== $newnode) {\n                    $newnode = static::importNewnode($newnode, $node, $i);\n                    $node->parentNode->insertBefore($newnode, $node);\n                    $newnodes[] = $newnode;\n                }\n            }\n        }\n        $content->clear();\n        $content->add($newnodes);\n        return $this;\n    }\n\n    /**\n     * Create a deep copy of the set of matched elements.\n     *\n     * Equivalent to clone() in jQuery (clone is not a valid PHP function name)\n     *\n     * @return HtmlPageCrawler\n     * @api\n     */\n    public function makeClone()\n    {\n        return clone $this;\n    }\n\n    public function __clone()\n    {\n        $newnodes = array();\n        foreach ($this as $node) {\n            /** @var \\DOMNode $node */\n            $newnodes[] = $node->cloneNode(true);\n        }\n        $this->clear();\n        $this->add($newnodes);\n    }\n\n    /**\n     * Get one CSS style property of the first element or set it for all elements in the list\n     *\n     * Function is here for compatibility with jQuery; it is the same as getStyle() and setStyle()\n     *\n     * @see HtmlPageCrawler::getStyle()\n     * @see HtmlPageCrawler::setStyle()\n     *\n     * @param string $key The name of the style property\n     * @param null|string $value The CSS value to set, or NULL to get the current value\n     * @return HtmlPageCrawler|string If no param is provided, returns the CSS styles of the first element\n     * @api\n     */\n    public function css($key, $value = null)\n    {\n        if (null === $value) {\n            return $this->getStyle($key);\n        } else {\n            return $this->setStyle($key, $value);\n        }\n    }\n\n    /**\n     * get one CSS style property of the first element\n     *\n     * @param string $key name of the property\n     * @return string|null value of the property\n     */\n    public function getStyle($key)\n    {\n        $styles = Helpers::cssStringToArray($this->getAttribute('style'));\n        return (isset($styles[$key]) ? $styles[$key] : null);\n    }\n\n    /**\n     * set one CSS style property for all elements in the list\n     *\n     * @param string $key name of the property\n     * @param string $value value of the property\n     * @return HtmlPageCrawler $this for chaining\n     */\n    public function setStyle($key, $value)\n    {\n        foreach ($this as $node) {\n            if ($node instanceof \\DOMElement) {\n                /** @var \\DOMElement $node */\n                $styles = Helpers::cssStringToArray($node->getAttribute('style'));\n                if ($value != '') {\n                    $styles[$key] = $value;\n                } elseif (isset($styles[$key])) {\n                    unset($styles[$key]);\n                }\n                $node->setAttribute('style', Helpers::cssArrayToString($styles));\n            }\n        }\n        return $this;\n    }\n\n    /**\n     * Removes all child nodes and text from all nodes in set\n     *\n     * Equivalent to jQuery's empty() function which is not a valid function name in PHP\n     * @return HtmlPageCrawler $this\n     * @api\n     */\n    public function makeEmpty()\n    {\n        foreach ($this as $node) {\n            $node->nodeValue = '';\n        }\n        return $this;\n    }\n\n    /**\n     * Determine whether any of the matched elements are assigned the given class.\n     *\n     * @param string $name\n     * @return bool\n     * @api\n     */\n    public function hasClass($name)\n    {\n        foreach ($this as $node) {\n            if ($node instanceof \\DOMElement && $class = $node->getAttribute('class')) {\n                $classes = preg_split('/\\s+/s', $class);\n                if (in_array($name, $classes)) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Set the HTML contents of each element\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content HTML code fragment\n     * @return HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function setInnerHtml($content)\n    {\n        $content = self::create($content);\n        foreach ($this as $node) {\n            $node->nodeValue = '';\n            foreach ($content as $newnode) {\n                /** @var \\DOMNode $node */\n                /** @var \\DOMNode $newnode */\n                $newnode = static::importNewnode($newnode, $node);\n                $node->appendChild($newnode);\n            }\n        }\n        return $this;\n    }\n\n    /**\n     * Alias for Crawler::html() for naming consistency with setInnerHtml()\n     *\n     * @return string\n     * @api\n     */\n    public function getInnerHtml()\n    {\n        return parent::html();\n    }\n\n    /**\n     * Insert every element in the set of matched elements after the target.\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode|\\DOMNodeList $element\n     * @return \\Wa72\\HtmlPageDom\\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements\n     * @api\n     */\n    public function insertAfter($element)\n    {\n        $e = self::create($element);\n        $newnodes = array();\n        foreach ($e as $i => $node) {\n            /** @var \\DOMNode $node */\n            $refnode = $node->nextSibling;\n            foreach ($this as $newnode) {\n                /** @var \\DOMNode $newnode */\n                $newnode = static::importNewnode($newnode, $node, $i);\n                if ($refnode === null) {\n                    $node->parentNode->appendChild($newnode);\n                } else {\n                    $node->parentNode->insertBefore($newnode, $refnode);\n                }\n                $newnodes[] = $newnode;\n            }\n        }\n        return self::create($newnodes);\n    }\n\n    /**\n     * Insert every element in the set of matched elements before the target.\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode|\\DOMNodeList $element\n     * @return \\Wa72\\HtmlPageDom\\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements\n     * @api\n     */\n    public function insertBefore($element)\n    {\n        $e = self::create($element);\n        $newnodes = array();\n        foreach ($e as $i => $node) {\n            /** @var \\DOMNode $node */\n            foreach ($this as $newnode) {\n                /** @var \\DOMNode $newnode */\n                $newnode = static::importNewnode($newnode, $node, $i);\n                if ($newnode !== $node) {\n                    $node->parentNode->insertBefore($newnode, $node);\n                }\n                $newnodes[] = $newnode;\n            }\n        }\n        return self::create($newnodes);\n    }\n\n    /**\n     * Insert content, specified by the parameter, to the beginning of each element in the set of matched elements.\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content HTML code fragment\n     * @return HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function prepend($content)\n    {\n        $content = self::create($content);\n        $newnodes = array();\n        foreach ($this as $i => $node) {\n            $refnode = $node->firstChild;\n            /** @var \\DOMNode $node */\n            foreach ($content as $newnode) {\n                /** @var \\DOMNode $newnode */\n                $newnode = static::importNewnode($newnode, $node, $i);\n                if ($refnode === null) {\n                    $node->appendChild($newnode);\n                } else if ($refnode !== $newnode) {\n                    $node->insertBefore($newnode, $refnode);\n                }\n                $newnodes[] = $newnode;\n            }\n        }\n        $content->clear();\n        $content->add($newnodes);\n        return $this;\n    }\n\n    /**\n     * Insert every element in the set of matched elements to the beginning of the target.\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode|\\DOMNodeList $element\n     * @return \\Wa72\\HtmlPageDom\\HtmlPageCrawler A new Crawler object containing all elements prepended to the target elements\n     * @api\n     */\n    public function prependTo($element)\n    {\n        $e = self::create($element);\n        $newnodes = array();\n        foreach ($e as $i => $node) {\n            $refnode = $node->firstChild;\n            /** @var \\DOMNode $node */\n            foreach ($this as $newnode) {\n                /** @var \\DOMNode $newnode */\n                $newnode = static::importNewnode($newnode, $node, $i);\n                if ($newnode !== $node) {\n                    if ($refnode === null) {\n                        $node->appendChild($newnode);\n                    } else {\n                        $node->insertBefore($newnode, $refnode);\n                    }\n                }\n                $newnodes[] = $newnode;\n            }\n        }\n        return self::create($newnodes);\n    }\n\n    /**\n     * Remove the set of matched elements from the DOM.\n     *\n     * (as opposed to Crawler::clear() which detaches the nodes only from Crawler\n     * but leaves them in the DOM)\n     *\n     * @api\n     */\n    public function remove()\n    {\n        foreach ($this as $node) {\n            /**\n             * @var \\DOMNode $node\n             */\n            if ($node->parentNode instanceof \\DOMElement) {\n                $node->parentNode->removeChild($node);\n            }\n        }\n        $this->clear();\n    }\n\n    /**\n     * Remove an attribute from each element in the set of matched elements.\n     *\n     * Alias for removeAttribute for compatibility with jQuery\n     *\n     * @param string $name\n     * @return HtmlPageCrawler\n     * @api\n     */\n    public function removeAttr($name)\n    {\n        return $this->removeAttribute($name);\n    }\n\n    /**\n     * Remove an attribute from each element in the set of matched elements.\n     *\n     * @param string $name\n     * @return HtmlPageCrawler\n     */\n    public function removeAttribute($name)\n    {\n        foreach ($this as $node) {\n            if ($node instanceof \\DOMElement) {\n                /** @var \\DOMElement $node */\n                if ($node->hasAttribute($name)) {\n                    $node->removeAttribute($name);\n                }\n            }\n        }\n        return $this;\n    }\n\n    /**\n     * Remove a class from each element in the list\n     *\n     * @param string $name\n     * @return HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function removeClass($name)\n    {\n        foreach ($this as $node) {\n            if ($node instanceof \\DOMElement) {\n                /** @var \\DOMElement $node */\n                $classes = preg_split('/\\s+/s', $node->getAttribute('class'));\n                $count = count($classes);\n                for ($i = 0; $i < $count; $i++) {\n                    if ($classes[$i] == $name) {\n                        unset($classes[$i]);\n                    }\n                }\n                $node->setAttribute('class', trim(join(' ', $classes)));\n            }\n        }\n        return $this;\n    }\n\n    /**\n     * Replace each target element with the set of matched elements.\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode|\\DOMNodeList $element\n     * @return \\Wa72\\HtmlPageDom\\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements\n     * @api\n     */\n    public function replaceAll($element)\n    {\n        $e = self::create($element);\n        $newnodes = array();\n        foreach ($e as $i => $node) {\n            /** @var \\DOMNode $node */\n            $parent = $node->parentNode;\n            $refnode  = $node->nextSibling;\n            foreach ($this as $j => $newnode) {\n                /** @var \\DOMNode $newnode */\n                $newnode = static::importNewnode($newnode, $node, $i);\n                if ($j == 0) {\n                    $parent->replaceChild($newnode, $node);\n                } else {\n                    $parent->insertBefore($newnode, $refnode);\n                }\n                $newnodes[] = $newnode;\n            }\n        }\n        return self::create($newnodes);\n    }\n\n    /**\n     * Replace each element in the set of matched elements with the provided new content and return the set of elements that was removed.\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content\n     * @return \\Wa72\\HtmlPageDom\\HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function replaceWith($content)\n    {\n        $content = self::create($content);\n        $newnodes = array();\n        foreach ($this as $i => $node) {\n            /** @var \\DOMNode $node */\n            $parent = $node->parentNode;\n            $refnode  = $node->nextSibling;\n            foreach ($content as $j => $newnode) {\n                /** @var \\DOMNode $newnode */\n                $newnode = static::importNewnode($newnode, $node, $i);\n                if ($j == 0) {\n                    $parent->replaceChild($newnode, $node);\n                } else {\n                    $parent->insertBefore($newnode, $refnode);\n                }\n                $newnodes[] = $newnode;\n            }\n        }\n        $content->clear();\n        $content->add($newnodes);\n        return $this;\n    }\n\n    /**\n     * Get the combined text contents of each element in the set of matched elements, including their descendants.\n     * This is what the jQuery text() function does, contrary to the Crawler::text() method that returns only\n     * the text of the first node.\n     *\n     * @return string\n     * @api\n     */\n    public function getCombinedText()\n    {\n        $text = '';\n        foreach ($this as $node) {\n            /** @var \\DOMNode $node */\n            $text .= $node->nodeValue;\n        }\n        return $text;\n    }\n\n    /**\n     * Set the text contents of the matched elements.\n     *\n     * @param string $text\n     * @return HtmlPageCrawler\n     * @api\n     */\n    public function setText($text)\n    {\n        $text = htmlspecialchars($text);\n        foreach ($this as $node) {\n            /** @var \\DOMNode $node */\n            $node->nodeValue = $text;\n        }\n        return $this;\n    }\n\n    /**\n     * Add or remove one or more classes from each element in the set of matched elements, depending the class’s presence.\n     *\n     * @param string $classname One or more classnames separated by spaces\n     * @return \\Wa72\\HtmlPageDom\\HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function toggleClass($classname)\n    {\n        $classes = explode(' ', $classname);\n        foreach ($this as $i => $node) {\n            $c = self::create($node);\n            /** @var \\DOMNode $node */\n            foreach ($classes as $class) {\n                if ($c->hasClass($class)) {\n                    $c->removeClass($class);\n                } else {\n                    $c->addClass($class);\n                }\n            }\n        }\n        return $this;\n    }\n\n    /**\n     * Remove the parents of the set of matched elements from the DOM, leaving the matched elements in their place.\n     *\n     * @return \\Wa72\\HtmlPageDom\\HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function unwrap()\n    {\n        $parents = array();\n        foreach($this as $i => $node) {\n            $parents[] = $node->parentNode;\n        }\n\n        self::create($parents)->unwrapInner();\n        return $this;\n    }\n\n    /**\n     * Remove the matched elements, but promote the children to take their place.\n     *\n     * @return \\Wa72\\HtmlPageDom\\HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function unwrapInner()\n    {\n        foreach($this as $i => $node) {\n            if (!$node->parentNode instanceof \\DOMElement) {\n                throw new \\InvalidArgumentException('DOMElement does not have a parent DOMElement node.');\n            }\n\n            /** @var \\DOMNode[] $children */\n            $children = iterator_to_array($node->childNodes);\n            foreach ($children as $child) {\n                $node->parentNode->insertBefore($child, $node);\n            }\n\n            $node->parentNode->removeChild($node);\n        }\n    }\n\n\n    /**\n     * Wrap an HTML structure around each element in the set of matched elements\n     *\n     * The HTML structure must contain only one root node, e.g.:\n     * Works: <div><div></div></div>\n     * Does not work: <div></div><div></div>\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode $wrappingElement\n     * @return HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function wrap($wrappingElement)\n    {\n        $content = self::create($wrappingElement);\n        $newnodes = array();\n        foreach ($this as $i => $node) {\n            /** @var \\DOMNode $node */\n            $newnode = $content->getNode(0);\n            /** @var \\DOMNode $newnode */\n//            $newnode = static::importNewnode($newnode, $node, $i);\n            if ($newnode->ownerDocument !== $node->ownerDocument) {\n                $newnode = $node->ownerDocument->importNode($newnode, true);\n            } else {\n                if ($i > 0) {\n                    $newnode = $newnode->cloneNode(true);\n                }\n            }\n            $oldnode = $node->parentNode->replaceChild($newnode, $node);\n            while ($newnode->hasChildNodes()) {\n                $elementFound = false;\n                foreach ($newnode->childNodes as $child) {\n                    if ($child instanceof \\DOMElement) {\n                        $newnode = $child;\n                        $elementFound = true;\n                        break;\n                    }\n                }\n                if (!$elementFound) {\n                    break;\n                }\n            }\n            $newnode->appendChild($oldnode);\n            $newnodes[] = $newnode;\n        }\n        $content->clear();\n        $content->add($newnodes);\n        return $this;\n    }\n\n    /**\n     * Wrap an HTML structure around all elements in the set of matched elements.\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content\n     * @throws \\LogicException\n     * @return \\Wa72\\HtmlPageDom\\HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function wrapAll($content)\n    {\n        $content = self::create($content);\n        $parent = $this->getNode(0)->parentNode;\n        foreach ($this as $i => $node) {\n            /** @var \\DOMNode $node */\n            if ($node->parentNode !== $parent) {\n                throw new \\LogicException('Nodes to be wrapped with wrapAll() must all have the same parent');\n            }\n        }\n\n        $newnode = $content->getNode(0);\n        /** @var \\DOMNode $newnode */\n        $newnode = static::importNewnode($newnode, $parent);\n\n        $newnode = $parent->insertBefore($newnode,$this->getNode(0));\n        $content->clear();\n        $content->add($newnode);\n\n        while ($newnode->hasChildNodes()) {\n            $elementFound = false;\n            foreach ($newnode->childNodes as $child) {\n                if ($child instanceof \\DOMElement) {\n                    $newnode = $child;\n                    $elementFound = true;\n                    break;\n                }\n            }\n            if (!$elementFound) {\n                break;\n            }\n        }\n        foreach ($this as $i => $node) {\n            /** @var \\DOMNode $node */\n            $newnode->appendChild($node);\n        }\n        return $this;\n    }\n\n    /**\n     * Wrap an HTML structure around the content of each element in the set of matched elements.\n     *\n     * @param string|HtmlPageCrawler|\\DOMNode|\\DOMNodeList $content\n     * @return \\Wa72\\HtmlPageDom\\HtmlPageCrawler $this for chaining\n     * @api\n     */\n    public function wrapInner($content)\n    {\n        foreach ($this as $i => $node) {\n            /** @var \\DOMNode $node */\n            self::create($node->childNodes)->wrapAll($content);\n        }\n        return $this;\n    }\n\n    /**\n     * Get the HTML code fragment of all elements and their contents.\n     *\n     * If the first node contains a complete HTML document return only\n     * the full code of this document.\n     *\n     * @return string HTML code (fragment)\n     * @api\n     */\n    public function saveHTML()\n    {\n        if ($this->isHtmlDocument()) {\n            return $this->getDOMDocument()->saveHTML();\n        } else {\n            $doc = new \\DOMDocument('1.0', 'UTF-8');\n            $root = $doc->appendChild($doc->createElement('_root'));\n            foreach ($this as $node) {\n                $root->appendChild($doc->importNode($node, true));\n            }\n            $html = trim($doc->saveHTML());\n            return preg_replace('@^<'.self::FRAGMENT_ROOT_TAGNAME.'[^>]*>|</'.self::FRAGMENT_ROOT_TAGNAME.'>$@', '', $html);\n        }\n    }\n\n    public function __toString()\n    {\n        return $this->saveHTML();\n    }\n\n    /**\n     * checks whether the first node contains a complete html document\n     * (as opposed to a document fragment)\n     *\n     * @return boolean\n     */\n    public function isHtmlDocument()\n    {\n        $node = $this->getNode(0);\n        if ($node instanceof \\DOMElement\n            && $node->ownerDocument instanceof \\DOMDocument\n            && $node->ownerDocument->documentElement === $node\n            && $node->nodeName == 'html'\n        ) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * get ownerDocument of the first element\n     *\n     * @return \\DOMDocument|null\n     */\n    public function getDOMDocument()\n    {\n        $node = $this->getNode(0);\n        $r = null;\n        if ($node instanceof \\DOMElement\n            && $node->ownerDocument instanceof \\DOMDocument\n        ) {\n            $r = $node->ownerDocument;\n        }\n        return $r;\n    }\n\n    /**\n     * Filters the list of nodes with a CSS selector.\n     *\n     * @param string $selector\n     * @return HtmlPageCrawler\n     */\n    public function filter(string $selector): static\n    {\n        return parent::filter($selector);\n    }\n\n    /**\n     * Filters the list of nodes with an XPath expression.\n     *\n     * @param string $xpath An XPath expression\n     *\n     * @return HtmlPageCrawler A new instance of Crawler with the filtered list of nodes\n     *\n     * @api\n     */\n    public function filterXPath($xpath): static\n    {\n        return parent::filterXPath($xpath);\n    }\n\n    /**\n     * Adds HTML/XML content to the HtmlPageCrawler object (but not to the DOM of an already attached node).\n     *\n     * Function overriden from Crawler because HTML fragments are always added as complete documents there\n     *\n     *\n     * @param string      $content A string to parse as HTML/XML\n     * @param null|string $type    The content type of the string\n     *\n     * @return null|void\n     */\n    public function addContent($content, $type = null): void\n    {\n        if (empty($type)) {\n            $type = 'text/html;charset=UTF-8';\n        }\n        if (substr($type, 0, 9) == 'text/html' && !preg_match('/<html\\b[^>]*>/i', $content)) {\n            // string contains no <html> Tag => no complete document but an HTML fragment!\n            $this->addHtmlFragment($content);\n        } else {\n            parent::addContent($content, $type);\n        }\n    }\n\n    public function addHtmlFragment($content, $charset = 'UTF-8')\n    {\n        $d = new \\DOMDocument('1.0', $charset);\n        $d->preserveWhiteSpace = false;\n        $root = $d->appendChild($d->createElement(self::FRAGMENT_ROOT_TAGNAME));\n        $bodynode = Helpers::getBodyNodeFromHtmlFragment($content, $charset);\n        foreach ($bodynode->childNodes as $child) {\n            $inode = $root->appendChild($d->importNode($child, true));\n            if ($inode) {\n                $this->addNode($inode);\n            }\n        }\n    }\n\n    /**\n     * Adds a node to the current list of nodes.\n     *\n     * This method uses the appropriate specialized add*() method based\n     * on the type of the argument.\n     *\n     * Overwritten from parent to allow Crawler to be added\n     *\n     * @param \\DOMNodeList|\\DOMNode|array|string|Crawler|null $node A node\n     *\n     * @api\n     */\n    public function add(\\DOMNodeList|\\DOMNode|array|string|Crawler|null $node): void\n    {\n        if ($node instanceof Crawler) {\n            foreach ($node as $childnode) {\n                $this->addNode($childnode);\n            }\n        } else {\n            parent::add($node);\n        }\n    }\n\n    /**\n     * @param \\DOMNode $newnode\n     * @param \\DOMNode $referencenode\n     * @param int $clone\n     * @return \\DOMNode\n     */\n    protected static function importNewnode(\\DOMNode $newnode, \\DOMNode $referencenode, $clone = 0) {\n        if ($newnode->ownerDocument !== $referencenode->ownerDocument) {\n            $referencenode->ownerDocument->preserveWhiteSpace = false;\n            $newnode = $referencenode->ownerDocument->importNode($newnode, true);\n        } else {\n            if ($clone > 0) {\n                $newnode = $newnode->cloneNode(true);\n            }\n        }\n        return $newnode;\n    }\n\n//    /**\n//     * Checks whether the first node in the set is disconnected (has no parent node)\n//     *\n//     * @return bool\n//     */\n//    public function isDisconnected()\n//    {\n//        $parent = $this->getNode(0)->parentNode;\n//        return ($parent == null || $parent->tagName == self::FRAGMENT_ROOT_TAGNAME);\n//    }\n\n    public function __get($name)\n    {\n        switch ($name) {\n            case 'count':\n            case 'length':\n                return count($this);\n        }\n        throw new \\Exception('No such property ' . $name);\n    }\n}\n"
  }
]