Gallery Custom Links - Version 0.0.1

Version Description

  • First release.

=

Download this release

Release Info

Developer TigrouMeow
Plugin Icon 128x128 Gallery Custom Links
Version 0.0.1
Comparing to
See all releases

Version 0.0.1

Files changed (41) hide show
  1. composer.json +5 -0
  2. composer.lock +64 -0
  3. gallery_custom_links.php +35 -0
  4. mgcl_core.php +104 -0
  5. readme.txt +44 -0
  6. vendor/autoload.php +7 -0
  7. vendor/composer/ClassLoader.php +445 -0
  8. vendor/composer/LICENSE +21 -0
  9. vendor/composer/autoload_classmap.php +9 -0
  10. vendor/composer/autoload_namespaces.php +9 -0
  11. vendor/composer/autoload_psr4.php +10 -0
  12. vendor/composer/autoload_real.php +52 -0
  13. vendor/composer/autoload_static.php +31 -0
  14. vendor/composer/installed.json +50 -0
  15. vendor/imangazaliev/didom/.php_cs +207 -0
  16. vendor/imangazaliev/didom/.travis.yml +20 -0
  17. vendor/imangazaliev/didom/CHANGELOG.md +211 -0
  18. vendor/imangazaliev/didom/LICENSE +19 -0
  19. vendor/imangazaliev/didom/README-RU.md +833 -0
  20. vendor/imangazaliev/didom/README.md +603 -0
  21. vendor/imangazaliev/didom/composer.json +30 -0
  22. vendor/imangazaliev/didom/phpunit.xml +17 -0
  23. vendor/imangazaliev/didom/src/DiDom/ClassAttribute.php +269 -0
  24. vendor/imangazaliev/didom/src/DiDom/Document.php +663 -0
  25. vendor/imangazaliev/didom/src/DiDom/Element.php +1445 -0
  26. vendor/imangazaliev/didom/src/DiDom/Encoder.php +58 -0
  27. vendor/imangazaliev/didom/src/DiDom/Errors.php +40 -0
  28. vendor/imangazaliev/didom/src/DiDom/Exceptions/InvalidSelectorException.php +10 -0
  29. vendor/imangazaliev/didom/src/DiDom/Query.php +559 -0
  30. vendor/imangazaliev/didom/src/DiDom/StyleAttribute.php +324 -0
  31. vendor/imangazaliev/didom/tests/DiDom/ClassAttributeTest.php +383 -0
  32. vendor/imangazaliev/didom/tests/DiDom/DocumentTest.php +709 -0
  33. vendor/imangazaliev/didom/tests/DiDom/ElementTest.php +2056 -0
  34. vendor/imangazaliev/didom/tests/DiDom/QueryTest.php +425 -0
  35. vendor/imangazaliev/didom/tests/DiDom/SelectorTest.php +327 -0
  36. vendor/imangazaliev/didom/tests/DiDom/StyleAttributeTest.php +515 -0
  37. vendor/imangazaliev/didom/tests/TestCase.php +40 -0
  38. vendor/imangazaliev/didom/tests/bootstrap.php +7 -0
  39. vendor/imangazaliev/didom/tests/fixtures/books.xml +120 -0
  40. vendor/imangazaliev/didom/tests/fixtures/menu.html +14 -0
  41. vendor/imangazaliev/didom/tests/fixtures/posts.html +25 -0
composer.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ {
2
+ "require": {
3
+ "imangazaliev/didom": "^1.13"
4
+ }
5
+ }
composer.lock ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_readme": [
3
+ "This file locks the dependencies of your project to a known state",
4
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5
+ "This file is @generated automatically"
6
+ ],
7
+ "content-hash": "f9f9f5cd3cfa4a3b2acc09d04bf4ec3f",
8
+ "packages": [
9
+ {
10
+ "name": "imangazaliev/didom",
11
+ "version": "1.13",
12
+ "source": {
13
+ "type": "git",
14
+ "url": "https://github.com/Imangazaliev/DiDOM.git",
15
+ "reference": "10c4033d56e599f09183959ed90bf17519b9e38b"
16
+ },
17
+ "dist": {
18
+ "type": "zip",
19
+ "url": "https://api.github.com/repos/Imangazaliev/DiDOM/zipball/10c4033d56e599f09183959ed90bf17519b9e38b",
20
+ "reference": "10c4033d56e599f09183959ed90bf17519b9e38b",
21
+ "shasum": ""
22
+ },
23
+ "require": {
24
+ "php": ">=5.4"
25
+ },
26
+ "require-dev": {
27
+ "phpunit/phpunit": "^4.8"
28
+ },
29
+ "type": "library",
30
+ "autoload": {
31
+ "psr-4": {
32
+ "DiDom\\": "src/DiDom/"
33
+ }
34
+ },
35
+ "notification-url": "https://packagist.org/downloads/",
36
+ "license": [
37
+ "MIT"
38
+ ],
39
+ "authors": [
40
+ {
41
+ "name": "Imangazaliev Muhammad",
42
+ "email": "imangazalievm@gmail.com"
43
+ }
44
+ ],
45
+ "description": "Simple and fast HTML parser",
46
+ "homepage": "https://github.com/Imangazaliev/DiDOM",
47
+ "keywords": [
48
+ "didom",
49
+ "html",
50
+ "parser",
51
+ "xml"
52
+ ],
53
+ "time": "2017-12-08T15:20:07+00:00"
54
+ }
55
+ ],
56
+ "packages-dev": [],
57
+ "aliases": [],
58
+ "minimum-stability": "stable",
59
+ "stability-flags": [],
60
+ "prefer-stable": false,
61
+ "prefer-lowest": false,
62
+ "platform": [],
63
+ "platform-dev": []
64
+ }
gallery_custom_links.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: Gallery Custom Links
4
+ Plugin URI: https://meowapps.com
5
+ Description: Gallery Custom Links allows you to link images from galleries to a specified URL. Tested with WordPress Gallery, Gutenberg, the Meow Gallery and others.
6
+ Version: 0.0.1
7
+ Author: Jordy Meow
8
+ Text Domain: gallery-custom-links
9
+ Domain Path: /languages
10
+
11
+ Dual licensed under the MIT and GPL licenses:
12
+ http://www.opensource.org/licenses/mit-license.php
13
+ http://www.gnu.org/licenses/gpl.html
14
+ */
15
+
16
+ if ( class_exists( 'Meow_Gallery_Custom_Links' ) ) {
17
+ function mfrh_admin_notices() {
18
+ echo '<div class="error"><p>Thanks for installing Gallery Custom Links :) However, another version is still enabled. Please disable or uninstall it.</p></div>';
19
+ }
20
+ add_action( 'admin_notices', 'mfrh_admin_notices' );
21
+ return;
22
+ }
23
+
24
+ global $mgcl_version;
25
+ $mgcl_version = '0.0.1';
26
+
27
+ // Admin
28
+ // include "mgcl_admin.php";
29
+ // $mgcl_admin = new Meow_Gallery_Custom_Links_Admin( 'mgcl', __FILE__, 'gallery-custom-links' );
30
+
31
+ // Core
32
+ include "mgcl_core.php";
33
+ $mgcl_core = new Meow_Gallery_Custom_Links();
34
+
35
+ ?>
mgcl_core.php ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once 'vendor/autoload.php';
4
+
5
+ use DiDom\Document;
6
+ use DiDom\Element;
7
+
8
+ class Meow_Gallery_Custom_Links
9
+ {
10
+ public function __construct() {
11
+ add_action( 'wp_head', array( $this, 'wp_head' ) );
12
+ add_action( 'wp_footer', array( $this, 'wp_footer' ) );
13
+ add_filter( 'attachment_fields_to_edit', array( $this, 'attachment_fields_to_edit' ), 10, 2 );
14
+ add_filter( 'attachment_fields_to_save', array( $this, 'apply_filter_attachment_fields_to_save' ), 10 , 2 );
15
+ }
16
+
17
+ function attachment_fields_to_edit( $fields, $post ) {
18
+ $fields['gallery_link_url'] = array(
19
+ 'label' => __( 'Link URL', 'gallery-custom-links' ),
20
+ 'input' => 'text',
21
+ 'value' => get_post_meta( $post->ID, '_gallery_link_url', true )
22
+ );
23
+ $target_value = get_post_meta( $post->ID, '_gallery_link_target', true );
24
+ $fields['gallery_link_target'] = array(
25
+ 'label' => __( 'Link Target', 'gallery-custom-links' ),
26
+ 'input' => 'html',
27
+ 'html' => '
28
+ <select name="attachments[' . $post->ID . '][gallery_link_target]" id="attachments[' . $post->ID . '][gallery_link_target]">
29
+ <option value="_self"' . ( $target_value == '_self' ? ' selected="selected"' : '' ) . '>' .
30
+ __( 'Same page', 'gallery-custom-links' ) .
31
+ '</option>
32
+ <option value="_blank"' . ( $target_value == '_blank' ? ' selected="selected"' : '' ) . '>' .
33
+ __( 'New page', 'gallery-custom-links' ) .
34
+ '</option>
35
+ </select>'
36
+ );
37
+ return $fields;
38
+ }
39
+
40
+ function apply_filter_attachment_fields_to_save( $post, $attachment ) {
41
+ if ( isset( $attachment['gallery_link_url'] ) )
42
+ update_post_meta( $post['ID'], '_gallery_link_url', $attachment['gallery_link_url'] );
43
+ if ( isset( $attachment['gallery_link_target'] ) )
44
+ update_post_meta( $post['ID'], '_gallery_link_target', $attachment['gallery_link_target'] );
45
+ return $post;
46
+ }
47
+
48
+ function wp_head() {
49
+ ob_start( array( $this, "linkify" ) );
50
+ }
51
+
52
+ function linkify_element( $element ) {
53
+ $classes = $element->attr('class');
54
+ if ( preg_match( '/wp-image-([0-9]{1,10})/i', $classes, $matches ) ) {
55
+ $url = get_post_meta( $matches[1], '_gallery_link_url', true );
56
+ if ( !empty( $url ) ) {
57
+ $target = get_post_meta( $matches[1], '_gallery_link_target', true );
58
+ if ( empty( $target ) )
59
+ $target = '_self';
60
+ $parent = $element->parent();
61
+ if ( $parent->tag === 'a' ) {
62
+ $parent->attr( 'href', $url );
63
+ $parent->attr( 'target', $target );
64
+ }
65
+ else {
66
+ if ( $parent->tag === 'figure' )
67
+ $parent = $parent->parent();
68
+ $a = new Element('a');
69
+ $a->attr( 'href', $url );
70
+ $a->attr( 'onclick', 'event.stopPropagation()' );
71
+ $a->attr( 'target', $target );
72
+ $a->appendChild( $parent->children() );
73
+ foreach( $parent->children() as $img ) {
74
+
75
+ $img->remove();
76
+ }
77
+ $parent->appendChild( $a );
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ function linkify( $buffer ) {
84
+ if ( !isset( $buffer ) || trim( $buffer ) === '' )
85
+ return $buffer;
86
+
87
+ $html = new Document( $buffer );
88
+ foreach ( $html->find('.wp-block-gallery img') as $element )
89
+ $this->linkify_element( $element );
90
+ foreach ( $html->find('.gallery img') as $element )
91
+ $this->linkify_element( $element );
92
+ if ( class_exists( 'Meow_Gallery_Core' ) ) {
93
+ foreach ( $html->find('.mgl-gallery img') as $element )
94
+ $this->linkify_element( $element );
95
+ }
96
+ return $html;
97
+ }
98
+
99
+ function wp_footer() {
100
+ @ob_end_flush();
101
+ }
102
+ }
103
+
104
+ ?>
readme.txt ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Gallery Custom Links ===
2
+ Contributors: TigrouMeow
3
+ Tags: custom, links, gallery, gutenberg
4
+ Requires at least: 4.9
5
+ Tested up to: 5.0
6
+ Stable tag: 0.0.1
7
+
8
+ Gallery Custom Links allows you to link images from galleries to a specified URL. Tested with WordPress Gallery, Gutenberg, the Meow Gallery and others.
9
+
10
+ == Description ==
11
+
12
+ Gallery Custom Links allows you to link images from galleries to a specified URL. Tested with WordPress Gallery, Gutenberg, the Meow Gallery and others.
13
+
14
+ === Usage ===
15
+
16
+ Two fields are added to your images, in your Media Library: Link URL and Link Target. If, at least, the Link URL is set up, this image will link to that URL every time it is used within a gallery. Lightbox will be automatically disabled for those images.
17
+
18
+ === Compatibility ===
19
+
20
+ It currently works with the native WP Gallery, the Gutenberg Gallery, and the Meow Gallery. It should actually work with any gallery plugin using the 'gallery' class and Responsive Images (src-set). Let me know if you would like more galleries to be supported, it should be easy. It does not work with NextGEN, as it does not work with Responsive Images.
21
+
22
+ === Thanks ===
23
+
24
+ The motivation to build this plugin came from my users who had issues trying to use WP Gallery Custom Links. I realized that this plugin was working extremely well with the standard gallery, but would require too much rewriting for Gutenberg and other galleries, hence the creation of this plugin. I hope it will help.
25
+
26
+ Languages: English.
27
+
28
+ == Installation ==
29
+
30
+ 1. Upload `gallery-custom-links` to the `/wp-content/plugins/` directory
31
+ 2. Activate the plugin through the 'Plugins' menu in WordPress
32
+
33
+ == Upgrade Notice ==
34
+
35
+ Replace all the files. Nothing else to do.
36
+
37
+ == Changelog ==
38
+
39
+ = 0.0.1 =
40
+ * First release.
41
+
42
+ == Screenshots ==
43
+
44
+ 1. The fields.
vendor/autoload.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload.php @generated by Composer
4
+
5
+ require_once __DIR__ . '/composer/autoload_real.php';
6
+
7
+ return ComposerAutoloaderInit2605e89cef9fc84e4fbf6431ef455676::getLoader();
vendor/composer/ClassLoader.php ADDED
@@ -0,0 +1,445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Composer.
5
+ *
6
+ * (c) Nils Adermann <naderman@naderman.de>
7
+ * Jordi Boggiano <j.boggiano@seld.be>
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ namespace Composer\Autoload;
14
+
15
+ /**
16
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17
+ *
18
+ * $loader = new \Composer\Autoload\ClassLoader();
19
+ *
20
+ * // register classes with namespaces
21
+ * $loader->add('Symfony\Component', __DIR__.'/component');
22
+ * $loader->add('Symfony', __DIR__.'/framework');
23
+ *
24
+ * // activate the autoloader
25
+ * $loader->register();
26
+ *
27
+ * // to enable searching the include path (eg. for PEAR packages)
28
+ * $loader->setUseIncludePath(true);
29
+ *
30
+ * In this example, if you try to use a class in the Symfony\Component
31
+ * namespace or one of its children (Symfony\Component\Console for instance),
32
+ * the autoloader will first look for the class under the component/
33
+ * directory, and it will then fallback to the framework/ directory if not
34
+ * found before giving up.
35
+ *
36
+ * This class is loosely based on the Symfony UniversalClassLoader.
37
+ *
38
+ * @author Fabien Potencier <fabien@symfony.com>
39
+ * @author Jordi Boggiano <j.boggiano@seld.be>
40
+ * @see http://www.php-fig.org/psr/psr-0/
41
+ * @see http://www.php-fig.org/psr/psr-4/
42
+ */
43
+ class ClassLoader
44
+ {
45
+ // PSR-4
46
+ private $prefixLengthsPsr4 = array();
47
+ private $prefixDirsPsr4 = array();
48
+ private $fallbackDirsPsr4 = array();
49
+
50
+ // PSR-0
51
+ private $prefixesPsr0 = array();
52
+ private $fallbackDirsPsr0 = array();
53
+
54
+ private $useIncludePath = false;
55
+ private $classMap = array();
56
+ private $classMapAuthoritative = false;
57
+ private $missingClasses = array();
58
+ private $apcuPrefix;
59
+
60
+ public function getPrefixes()
61
+ {
62
+ if (!empty($this->prefixesPsr0)) {
63
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
64
+ }
65
+
66
+ return array();
67
+ }
68
+
69
+ public function getPrefixesPsr4()
70
+ {
71
+ return $this->prefixDirsPsr4;
72
+ }
73
+
74
+ public function getFallbackDirs()
75
+ {
76
+ return $this->fallbackDirsPsr0;
77
+ }
78
+
79
+ public function getFallbackDirsPsr4()
80
+ {
81
+ return $this->fallbackDirsPsr4;
82
+ }
83
+
84
+ public function getClassMap()
85
+ {
86
+ return $this->classMap;
87
+ }
88
+
89
+ /**
90
+ * @param array $classMap Class to filename map
91
+ */
92
+ public function addClassMap(array $classMap)
93
+ {
94
+ if ($this->classMap) {
95
+ $this->classMap = array_merge($this->classMap, $classMap);
96
+ } else {
97
+ $this->classMap = $classMap;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Registers a set of PSR-0 directories for a given prefix, either
103
+ * appending or prepending to the ones previously set for this prefix.
104
+ *
105
+ * @param string $prefix The prefix
106
+ * @param array|string $paths The PSR-0 root directories
107
+ * @param bool $prepend Whether to prepend the directories
108
+ */
109
+ public function add($prefix, $paths, $prepend = false)
110
+ {
111
+ if (!$prefix) {
112
+ if ($prepend) {
113
+ $this->fallbackDirsPsr0 = array_merge(
114
+ (array) $paths,
115
+ $this->fallbackDirsPsr0
116
+ );
117
+ } else {
118
+ $this->fallbackDirsPsr0 = array_merge(
119
+ $this->fallbackDirsPsr0,
120
+ (array) $paths
121
+ );
122
+ }
123
+
124
+ return;
125
+ }
126
+
127
+ $first = $prefix[0];
128
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
129
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
130
+
131
+ return;
132
+ }
133
+ if ($prepend) {
134
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
135
+ (array) $paths,
136
+ $this->prefixesPsr0[$first][$prefix]
137
+ );
138
+ } else {
139
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
140
+ $this->prefixesPsr0[$first][$prefix],
141
+ (array) $paths
142
+ );
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Registers a set of PSR-4 directories for a given namespace, either
148
+ * appending or prepending to the ones previously set for this namespace.
149
+ *
150
+ * @param string $prefix The prefix/namespace, with trailing '\\'
151
+ * @param array|string $paths The PSR-4 base directories
152
+ * @param bool $prepend Whether to prepend the directories
153
+ *
154
+ * @throws \InvalidArgumentException
155
+ */
156
+ public function addPsr4($prefix, $paths, $prepend = false)
157
+ {
158
+ if (!$prefix) {
159
+ // Register directories for the root namespace.
160
+ if ($prepend) {
161
+ $this->fallbackDirsPsr4 = array_merge(
162
+ (array) $paths,
163
+ $this->fallbackDirsPsr4
164
+ );
165
+ } else {
166
+ $this->fallbackDirsPsr4 = array_merge(
167
+ $this->fallbackDirsPsr4,
168
+ (array) $paths
169
+ );
170
+ }
171
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
172
+ // Register directories for a new namespace.
173
+ $length = strlen($prefix);
174
+ if ('\\' !== $prefix[$length - 1]) {
175
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
176
+ }
177
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
178
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
179
+ } elseif ($prepend) {
180
+ // Prepend directories for an already registered namespace.
181
+ $this->prefixDirsPsr4[$prefix] = array_merge(
182
+ (array) $paths,
183
+ $this->prefixDirsPsr4[$prefix]
184
+ );
185
+ } else {
186
+ // Append directories for an already registered namespace.
187
+ $this->prefixDirsPsr4[$prefix] = array_merge(
188
+ $this->prefixDirsPsr4[$prefix],
189
+ (array) $paths
190
+ );
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Registers a set of PSR-0 directories for a given prefix,
196
+ * replacing any others previously set for this prefix.
197
+ *
198
+ * @param string $prefix The prefix
199
+ * @param array|string $paths The PSR-0 base directories
200
+ */
201
+ public function set($prefix, $paths)
202
+ {
203
+ if (!$prefix) {
204
+ $this->fallbackDirsPsr0 = (array) $paths;
205
+ } else {
206
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Registers a set of PSR-4 directories for a given namespace,
212
+ * replacing any others previously set for this namespace.
213
+ *
214
+ * @param string $prefix The prefix/namespace, with trailing '\\'
215
+ * @param array|string $paths The PSR-4 base directories
216
+ *
217
+ * @throws \InvalidArgumentException
218
+ */
219
+ public function setPsr4($prefix, $paths)
220
+ {
221
+ if (!$prefix) {
222
+ $this->fallbackDirsPsr4 = (array) $paths;
223
+ } else {
224
+ $length = strlen($prefix);
225
+ if ('\\' !== $prefix[$length - 1]) {
226
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
227
+ }
228
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
229
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Turns on searching the include path for class files.
235
+ *
236
+ * @param bool $useIncludePath
237
+ */
238
+ public function setUseIncludePath($useIncludePath)
239
+ {
240
+ $this->useIncludePath = $useIncludePath;
241
+ }
242
+
243
+ /**
244
+ * Can be used to check if the autoloader uses the include path to check
245
+ * for classes.
246
+ *
247
+ * @return bool
248
+ */
249
+ public function getUseIncludePath()
250
+ {
251
+ return $this->useIncludePath;
252
+ }
253
+
254
+ /**
255
+ * Turns off searching the prefix and fallback directories for classes
256
+ * that have not been registered with the class map.
257
+ *
258
+ * @param bool $classMapAuthoritative
259
+ */
260
+ public function setClassMapAuthoritative($classMapAuthoritative)
261
+ {
262
+ $this->classMapAuthoritative = $classMapAuthoritative;
263
+ }
264
+
265
+ /**
266
+ * Should class lookup fail if not found in the current class map?
267
+ *
268
+ * @return bool
269
+ */
270
+ public function isClassMapAuthoritative()
271
+ {
272
+ return $this->classMapAuthoritative;
273
+ }
274
+
275
+ /**
276
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
277
+ *
278
+ * @param string|null $apcuPrefix
279
+ */
280
+ public function setApcuPrefix($apcuPrefix)
281
+ {
282
+ $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
283
+ }
284
+
285
+ /**
286
+ * The APCu prefix in use, or null if APCu caching is not enabled.
287
+ *
288
+ * @return string|null
289
+ */
290
+ public function getApcuPrefix()
291
+ {
292
+ return $this->apcuPrefix;
293
+ }
294
+
295
+ /**
296
+ * Registers this instance as an autoloader.
297
+ *
298
+ * @param bool $prepend Whether to prepend the autoloader or not
299
+ */
300
+ public function register($prepend = false)
301
+ {
302
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
303
+ }
304
+
305
+ /**
306
+ * Unregisters this instance as an autoloader.
307
+ */
308
+ public function unregister()
309
+ {
310
+ spl_autoload_unregister(array($this, 'loadClass'));
311
+ }
312
+
313
+ /**
314
+ * Loads the given class or interface.
315
+ *
316
+ * @param string $class The name of the class
317
+ * @return bool|null True if loaded, null otherwise
318
+ */
319
+ public function loadClass($class)
320
+ {
321
+ if ($file = $this->findFile($class)) {
322
+ includeFile($file);
323
+
324
+ return true;
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Finds the path to the file where the class is defined.
330
+ *
331
+ * @param string $class The name of the class
332
+ *
333
+ * @return string|false The path if found, false otherwise
334
+ */
335
+ public function findFile($class)
336
+ {
337
+ // class map lookup
338
+ if (isset($this->classMap[$class])) {
339
+ return $this->classMap[$class];
340
+ }
341
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
342
+ return false;
343
+ }
344
+ if (null !== $this->apcuPrefix) {
345
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
346
+ if ($hit) {
347
+ return $file;
348
+ }
349
+ }
350
+
351
+ $file = $this->findFileWithExtension($class, '.php');
352
+
353
+ // Search for Hack files if we are running on HHVM
354
+ if (false === $file && defined('HHVM_VERSION')) {
355
+ $file = $this->findFileWithExtension($class, '.hh');
356
+ }
357
+
358
+ if (null !== $this->apcuPrefix) {
359
+ apcu_add($this->apcuPrefix.$class, $file);
360
+ }
361
+
362
+ if (false === $file) {
363
+ // Remember that this class does not exist.
364
+ $this->missingClasses[$class] = true;
365
+ }
366
+
367
+ return $file;
368
+ }
369
+
370
+ private function findFileWithExtension($class, $ext)
371
+ {
372
+ // PSR-4 lookup
373
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
374
+
375
+ $first = $class[0];
376
+ if (isset($this->prefixLengthsPsr4[$first])) {
377
+ $subPath = $class;
378
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
379
+ $subPath = substr($subPath, 0, $lastPos);
380
+ $search = $subPath . '\\';
381
+ if (isset($this->prefixDirsPsr4[$search])) {
382
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
383
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
384
+ if (file_exists($file = $dir . $pathEnd)) {
385
+ return $file;
386
+ }
387
+ }
388
+ }
389
+ }
390
+ }
391
+
392
+ // PSR-4 fallback dirs
393
+ foreach ($this->fallbackDirsPsr4 as $dir) {
394
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
395
+ return $file;
396
+ }
397
+ }
398
+
399
+ // PSR-0 lookup
400
+ if (false !== $pos = strrpos($class, '\\')) {
401
+ // namespaced class name
402
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
403
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
404
+ } else {
405
+ // PEAR-like class name
406
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
407
+ }
408
+
409
+ if (isset($this->prefixesPsr0[$first])) {
410
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
411
+ if (0 === strpos($class, $prefix)) {
412
+ foreach ($dirs as $dir) {
413
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
414
+ return $file;
415
+ }
416
+ }
417
+ }
418
+ }
419
+ }
420
+
421
+ // PSR-0 fallback dirs
422
+ foreach ($this->fallbackDirsPsr0 as $dir) {
423
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
424
+ return $file;
425
+ }
426
+ }
427
+
428
+ // PSR-0 include paths.
429
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
430
+ return $file;
431
+ }
432
+
433
+ return false;
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Scope isolated include.
439
+ *
440
+ * Prevents access to $this/self from included files.
441
+ */
442
+ function includeFile($file)
443
+ {
444
+ include $file;
445
+ }
vendor/composer/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Copyright (c) Nils Adermann, Jordi Boggiano
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished
9
+ to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+
vendor/composer/autoload_classmap.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_classmap.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ );
vendor/composer/autoload_namespaces.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_namespaces.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ );
vendor/composer/autoload_psr4.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_psr4.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ 'DiDom\\' => array($vendorDir . '/imangazaliev/didom/src/DiDom'),
10
+ );
vendor/composer/autoload_real.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_real.php @generated by Composer
4
+
5
+ class ComposerAutoloaderInit2605e89cef9fc84e4fbf6431ef455676
6
+ {
7
+ private static $loader;
8
+
9
+ public static function loadClassLoader($class)
10
+ {
11
+ if ('Composer\Autoload\ClassLoader' === $class) {
12
+ require __DIR__ . '/ClassLoader.php';
13
+ }
14
+ }
15
+
16
+ public static function getLoader()
17
+ {
18
+ if (null !== self::$loader) {
19
+ return self::$loader;
20
+ }
21
+
22
+ spl_autoload_register(array('ComposerAutoloaderInit2605e89cef9fc84e4fbf6431ef455676', 'loadClassLoader'), true, true);
23
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
+ spl_autoload_unregister(array('ComposerAutoloaderInit2605e89cef9fc84e4fbf6431ef455676', 'loadClassLoader'));
25
+
26
+ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
27
+ if ($useStaticLoader) {
28
+ require_once __DIR__ . '/autoload_static.php';
29
+
30
+ call_user_func(\Composer\Autoload\ComposerStaticInit2605e89cef9fc84e4fbf6431ef455676::getInitializer($loader));
31
+ } else {
32
+ $map = require __DIR__ . '/autoload_namespaces.php';
33
+ foreach ($map as $namespace => $path) {
34
+ $loader->set($namespace, $path);
35
+ }
36
+
37
+ $map = require __DIR__ . '/autoload_psr4.php';
38
+ foreach ($map as $namespace => $path) {
39
+ $loader->setPsr4($namespace, $path);
40
+ }
41
+
42
+ $classMap = require __DIR__ . '/autoload_classmap.php';
43
+ if ($classMap) {
44
+ $loader->addClassMap($classMap);
45
+ }
46
+ }
47
+
48
+ $loader->register(true);
49
+
50
+ return $loader;
51
+ }
52
+ }
vendor/composer/autoload_static.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_static.php @generated by Composer
4
+
5
+ namespace Composer\Autoload;
6
+
7
+ class ComposerStaticInit2605e89cef9fc84e4fbf6431ef455676
8
+ {
9
+ public static $prefixLengthsPsr4 = array (
10
+ 'D' =>
11
+ array (
12
+ 'DiDom\\' => 6,
13
+ ),
14
+ );
15
+
16
+ public static $prefixDirsPsr4 = array (
17
+ 'DiDom\\' =>
18
+ array (
19
+ 0 => __DIR__ . '/..' . '/imangazaliev/didom/src/DiDom',
20
+ ),
21
+ );
22
+
23
+ public static function getInitializer(ClassLoader $loader)
24
+ {
25
+ return \Closure::bind(function () use ($loader) {
26
+ $loader->prefixLengthsPsr4 = ComposerStaticInit2605e89cef9fc84e4fbf6431ef455676::$prefixLengthsPsr4;
27
+ $loader->prefixDirsPsr4 = ComposerStaticInit2605e89cef9fc84e4fbf6431ef455676::$prefixDirsPsr4;
28
+
29
+ }, null, ClassLoader::class);
30
+ }
31
+ }
vendor/composer/installed.json ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "name": "imangazaliev/didom",
4
+ "version": "1.13",
5
+ "version_normalized": "1.13.0.0",
6
+ "source": {
7
+ "type": "git",
8
+ "url": "https://github.com/Imangazaliev/DiDOM.git",
9
+ "reference": "10c4033d56e599f09183959ed90bf17519b9e38b"
10
+ },
11
+ "dist": {
12
+ "type": "zip",
13
+ "url": "https://api.github.com/repos/Imangazaliev/DiDOM/zipball/10c4033d56e599f09183959ed90bf17519b9e38b",
14
+ "reference": "10c4033d56e599f09183959ed90bf17519b9e38b",
15
+ "shasum": ""
16
+ },
17
+ "require": {
18
+ "php": ">=5.4"
19
+ },
20
+ "require-dev": {
21
+ "phpunit/phpunit": "^4.8"
22
+ },
23
+ "time": "2017-12-08T15:20:07+00:00",
24
+ "type": "library",
25
+ "installation-source": "dist",
26
+ "autoload": {
27
+ "psr-4": {
28
+ "DiDom\\": "src/DiDom/"
29
+ }
30
+ },
31
+ "notification-url": "https://packagist.org/downloads/",
32
+ "license": [
33
+ "MIT"
34
+ ],
35
+ "authors": [
36
+ {
37
+ "name": "Imangazaliev Muhammad",
38
+ "email": "imangazalievm@gmail.com"
39
+ }
40
+ ],
41
+ "description": "Simple and fast HTML parser",
42
+ "homepage": "https://github.com/Imangazaliev/DiDOM",
43
+ "keywords": [
44
+ "didom",
45
+ "html",
46
+ "parser",
47
+ "xml"
48
+ ]
49
+ }
50
+ ]
vendor/imangazaliev/didom/.php_cs ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ use PhpCsFixer\Config;
4
+ use PhpCsFixer\Finder;
5
+
6
+ require __DIR__.'/vendor/autoload.php';
7
+
8
+ $fixers = [
9
+ // PHP arrays should be declared using the short syntax
10
+ 'array_syntax' => ['syntax' => 'short'],
11
+
12
+ // There MUST be one blank line after the namespace declaration
13
+ 'blank_line_after_namespace' => true,
14
+
15
+ // Ensure there is no code on the same line as the PHP open tag and it is followed by a blank line
16
+ 'blank_line_after_opening_tag' => true,
17
+
18
+ // An empty line feed should precede a return statement
19
+ 'blank_line_before_return' => true,
20
+
21
+ // The body of each structure MUST be enclosed by braces
22
+ // Braces should be properly placed
23
+ // Body of braces should be properly indented
24
+ 'braces' => true,
25
+
26
+ // A single space should be between cast and variable
27
+ 'cast_spaces' => true,
28
+
29
+ // Whitespace around the key words of a class, trait or interfaces definition should be one space
30
+ 'class_definition' => true,
31
+
32
+ // The keyword elseif should be used instead of else if so that all control keywords look like single words
33
+ 'elseif' => true,
34
+
35
+ // PHP code MUST use only UTF-8 without BOM (remove BOM)
36
+ 'encoding' => true,
37
+
38
+ // PHP code must use the long <?php tags or short-echo <?= tags and not other tag variations
39
+ 'full_opening_tag' => true,
40
+
41
+ // Spaces should be properly placed in a function declaration
42
+ 'function_declaration' => true,
43
+
44
+ // Add missing space between function's argument and its typehint
45
+ 'function_typehint_space' => true,
46
+
47
+ // Include/Require and file path should be divided with a single space
48
+ // File path should not be placed under brackets
49
+ 'include' => true,
50
+
51
+ // Code MUST use configured indentation type
52
+ 'indentation_type' => true,
53
+
54
+ // All PHP files must use same line ending
55
+ 'line_ending' => true,
56
+
57
+ // The PHP constants true, false, and null MUST be in lower case
58
+ 'lowercase_constants' => true,
59
+
60
+ // PHP keywords MUST be in lower case
61
+ 'lowercase_keywords' => true,
62
+
63
+ // In method arguments and method call, there MUST NOT be a space before each comma and there MUST be one space after each comma
64
+ 'method_argument_space' => true,
65
+
66
+ // Methods must be separated with one blank line
67
+ 'method_separation' => true,
68
+
69
+ // There should be no empty lines after class opening brace
70
+ 'no_blank_lines_after_class_opening' => true,
71
+
72
+ // There should not be blank lines between docblock and the documented element
73
+ 'no_blank_lines_after_phpdoc' => true,
74
+
75
+ // The closing PHP tag MUST be omitted from files containing only PHP
76
+ 'no_closing_tag' => true,
77
+
78
+ // Remove leading slashes in use clauses
79
+ 'no_leading_import_slash' => true,
80
+
81
+ // The namespace declaration line shouldn't contain leading whitespace
82
+ 'no_leading_namespace_whitespace' => true,
83
+
84
+ // Multi-line whitespace before closing semicolon are prohibited
85
+ 'no_multiline_whitespace_before_semicolons' => true,
86
+
87
+ // Single-line whitespace before closing semicolon are prohibited
88
+ 'no_singleline_whitespace_before_semicolons' => true,
89
+
90
+ // There MUST NOT be a space after the opening parenthesis
91
+ // There MUST NOT be a space before the closing parenthesis
92
+ 'no_spaces_inside_parenthesis' => true,
93
+
94
+ // Remove trailing commas in list function calls
95
+ 'no_trailing_comma_in_list_call' => true,
96
+
97
+ // PHP single-line arrays should not have trailing comma
98
+ 'no_trailing_comma_in_singleline_array' => true,
99
+
100
+ // Remove trailing commas in list function calls
101
+ 'no_trailing_whitespace' => true,
102
+
103
+ // Unused use statements must be removed
104
+ 'no_unused_imports' => true,
105
+
106
+ // Remove trailing whitespace at the end of blank lines
107
+ 'no_whitespace_in_blank_line' => true,
108
+
109
+ // All instances created with new keyword must be followed by braces
110
+ 'new_with_braces' => true,
111
+
112
+ // There should not be space before or after object T_OBJECT_OPERATOR ->
113
+ 'object_operator_without_whitespace' => true,
114
+
115
+ // Ordering use statements
116
+ 'ordered_imports' => true,
117
+
118
+ // All items of the given phpdoc tags must be aligned vertically
119
+ // defaults to ['param', 'return', 'throws', 'type', 'var']
120
+ // 'phpdoc_align' => true,
121
+
122
+ // Docblocks should have the same indentation as the documented subject
123
+ 'phpdoc_indent' => true,
124
+
125
+ // Fix PHPDoc inline tags, make inheritdoc always inline
126
+ 'phpdoc_inline_tag' => true,
127
+
128
+ // @access annotations should be omitted from phpdocs
129
+ 'phpdoc_no_access' => true,
130
+
131
+ // @return void and @return null annotations should be omitted from phpdocs
132
+ 'phpdoc_no_empty_return' => true,
133
+
134
+ // @package and @subpackage annotations should be omitted from phpdocs
135
+ 'phpdoc_no_package' => true,
136
+
137
+ // Scalar types should always be written in the same form
138
+ // int not integer, bool not boolean, float not real or double
139
+ 'phpdoc_scalar' => true,
140
+
141
+ // Annotations in phpdocs should be grouped together so that annotations of the same type immediately follow each other,
142
+ // and annotations of a different type are separated by a single blank line
143
+ 'phpdoc_separation' => true,
144
+
145
+ // Phpdocs summary should end in either a full stop, exclamation mark, or question mark
146
+ 'phpdoc_summary' => true,
147
+
148
+ // Docblocks should only be used on structural elements
149
+ 'phpdoc_to_comment' => true,
150
+
151
+ // Phpdocs should start and end with content, excluding the very first and last line of the docblocks
152
+ 'phpdoc_trim' => true,
153
+
154
+ // @var and @type annotations should not contain the variable name
155
+ 'phpdoc_var_without_name' => true,
156
+
157
+ // Pre incrementation/decrementation should be used if possible
158
+ 'pre_increment' => true,
159
+
160
+ // A PHP file without end tag must always end with a single empty line feed
161
+ 'single_blank_line_at_eof' => true,
162
+
163
+ // There should be exactly one blank line before a namespace declaration
164
+ 'single_blank_line_before_namespace' => true,
165
+
166
+ // There MUST be one use keyword per declaration
167
+ 'single_import_per_statement' => true,
168
+
169
+ // Each namespace use MUST go on its own line and there MUST be one blank line after the use statements block
170
+ 'single_line_after_imports' => true,
171
+
172
+ // Single-line comments and multi-line comments with only one line of actual content should use the // syntax
173
+ 'single_line_comment_style' => true,
174
+
175
+ // Convert double quotes to single quotes for simple strings
176
+ 'single_quote' => true,
177
+
178
+ // Replace all <> with !=
179
+ 'standardize_not_equals' => true,
180
+
181
+ // Standardize spaces around ternary operator
182
+ 'ternary_operator_spaces' => true,
183
+
184
+ // PHP multi-line arrays should have a trailing comma
185
+ 'trailing_comma_in_multiline_array' => true,
186
+
187
+ // Unary operators should be placed adjacent to their operands
188
+ 'unary_operator_spaces' => true,
189
+
190
+ // Visibility MUST be declared on all properties and methods;
191
+ // abstract and final MUST be declared before the visibility;
192
+ // static MUST be declared after the visibility
193
+ 'visibility_required' => true,
194
+ ];
195
+
196
+ $finder = Finder::create();
197
+
198
+ $finder->files()->in([
199
+ 'src',
200
+ ]);
201
+
202
+ $config = Config::create()
203
+ ->setRules($fixers)
204
+ ->setFinder($finder)
205
+ ->setUsingCache(true);
206
+
207
+ return $config;
vendor/imangazaliev/didom/.travis.yml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ language: php
2
+
3
+ php:
4
+ - 5.4
5
+ - 5.5.9
6
+ - 5.5
7
+ - 5.6
8
+ - 7.0
9
+
10
+ before_script:
11
+ - composer self-update
12
+ - composer install
13
+
14
+ script:
15
+ - phpunit
16
+
17
+ matrix:
18
+ allow_failures:
19
+ - php: 5.4
20
+ - php: 7.0
vendor/imangazaliev/didom/CHANGELOG.md ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### 1.13
2
+
3
+ - Add `Element::outerHtml()` method
4
+ - Add `Element::prependChild()` method
5
+ - Add `Element::insertBefore()` and `Element::insertAfter()` methods
6
+ - Add `Element::style()` method for more convenient inline styles manipulation
7
+ - Add `Element::classes()` method for more convenient class manipulation
8
+
9
+ ### 1.12
10
+
11
+ - Many fixes and improvements
12
+
13
+ ### 1.11.1
14
+
15
+ - Fix bug with unregistered PHP functions in XPath in `Document::has()` and `Document::count()` methods
16
+
17
+ ### 1.11
18
+
19
+ - Add `Element::isElementNode()` method
20
+ - Add ability to retrieve only specific attributes in `Element::attributes()` method
21
+ - Add `Element::removeAllAttributes()` method
22
+ - Add ability to specify selector and node type in `Element::previousSibling()` and `Element::nextSibling()` methods
23
+ - Add `Element::previousSiblings()` and `Element::nextSiblings()` methods
24
+ - Many minor fixes and improvements
25
+
26
+ ### 1.10.6
27
+
28
+ - Fix bug with XML document loading
29
+
30
+ ### v1.10.5
31
+
32
+ - Fix issue #85
33
+
34
+ ### 1.10.4
35
+
36
+ - Use `mb_convert_encoding()` in the Encoder if it is available
37
+
38
+ ### v1.10.3
39
+
40
+ - Add `Element::removeChild()` and `Element::removeChildren()` methods
41
+ - Fix bug in `Element::matches()` method
42
+ - `Element::matches()` method now returns false if node is not `DOMElement`
43
+ - Add `Element::hasChildren()` method
44
+
45
+ ### 1.10.2
46
+
47
+ - Fix bug in setInnerHtml: can't rewrite existing content
48
+ - Throw `InvalidSelectorException` instead of `InvalidArgumentException` when selector is empty
49
+
50
+ ### 1.10.1
51
+
52
+ - Fix attributes `ends-with` XPath
53
+ - Method `Element::matches()` now can check children nodes
54
+
55
+ ### 1.10
56
+
57
+ - Fix HTML saving mechanism
58
+ - Throw `InvalidSelectorException` instead of `RuntimeException` in Query class
59
+
60
+ ### 1.9.1
61
+
62
+ - Add ability to search in owner document using current node as context
63
+ - Bugs fixed
64
+
65
+ ### 1.9.0
66
+
67
+ - Methods `Document::appendChild()` and `Element::appendChild()` now return appended node(s)
68
+ - Add ability to search elements in context
69
+
70
+ ### 1.8.8
71
+
72
+ - Bugs fixed
73
+
74
+ ### 1.8.7
75
+
76
+ - Add `Element::getLineNo()` method
77
+
78
+ ### 1.8.6
79
+
80
+ - Fix issue #55
81
+
82
+ ### 1.8.5
83
+
84
+ - Add support of `DOMComment`
85
+
86
+ ### 1.8.4
87
+
88
+ - Add ability to create an element by selector
89
+ - Add closest method
90
+
91
+ ### 1.8.3
92
+
93
+ - Add method `Element::isTextNode()`
94
+ - Many minor fixes
95
+
96
+ ### 1.8.2
97
+
98
+ - Add ability to check that element matches selector
99
+ - Add ability counting nodes by selector
100
+ - Many minor fixes
101
+
102
+ ### 1.8.1
103
+
104
+ - Small fix
105
+
106
+ ### 1.8
107
+
108
+ - Bug fixes
109
+ - Add support of ~ selector
110
+ - Add ability to direct search by CSS selector
111
+ - Add setInnerHtml method
112
+ - Add attributes method
113
+
114
+ ### 1.7.4
115
+
116
+ - Add support of text nodes
117
+
118
+ ### 1.7.3
119
+
120
+ - Bug fix
121
+
122
+ ### 1.7.2
123
+
124
+ - Fixed behavior of nth-child pseudo class
125
+ - Add nth-of-type pseudo class
126
+
127
+ ### 1.7.1
128
+
129
+ - Add pseudo class has and more attribute options
130
+
131
+ ### 1.7.0
132
+
133
+ - Bug fixes
134
+ - Add methods `previousSibling`, `nextSibling`, `child`, `firstChild`, `lastChild`, `children`, `getDocument` to the Element
135
+ - Changed behavior of parent method. Now it returns parent node instead of owner document
136
+
137
+ ### 1.6.8
138
+
139
+ - Bug fix
140
+
141
+ ### 1.6.5
142
+
143
+ - Added ability to get an element attribute by CSS selector
144
+
145
+ ### 1.6.4
146
+
147
+ - Added handling of `DOMText` and `DOMAttr` in `Document::find()`
148
+
149
+ ### 1.6.3
150
+
151
+ - Added ability to get inner HTML
152
+
153
+ ### 1.6.2
154
+
155
+ - Added the ability to pass options when load HTML or XML
156
+
157
+ ### 1.6.1
158
+
159
+ - Added the ability to pass an array of nodes to appendChild
160
+ - Added the ability to pass options when converting to HTML or XML
161
+ - Added the ability to add child elements to the element
162
+
163
+ ### 1.6
164
+
165
+ - Added support for XML
166
+ - Added the ability to search element by part of attribute name or value
167
+ - Added support for pseudo-class "contains"
168
+ - Added the ability to clone a node
169
+
170
+ ### 1.5.1
171
+
172
+ - Added ability to remove and replace nodes
173
+ - Added ability to specify encoding when converting the element into the document
174
+
175
+ ### 1.5
176
+
177
+ - Fixed problem with incorrect encoding
178
+ - Added ability to set the value of the element
179
+ - Added ability to specify encoding when creating document
180
+
181
+ ### 1.4
182
+
183
+ - Added the ability to specify the return type element (`DiDom\Element` or `DOMElement`)
184
+
185
+ ### 1.3.2
186
+
187
+ - Bug fixed
188
+
189
+ ### 1.3.1
190
+
191
+ - Bugs fixed
192
+ - Added the ability to pass element attributes in the constructor
193
+
194
+ ### 1.3
195
+
196
+ - Bugs fixed
197
+
198
+ ### 1.2
199
+
200
+ - Bugs fixed
201
+ - Added the ability to compare Element\Document
202
+ - Added the ability to format HTML code of the document when outputting
203
+
204
+ ### 1.1
205
+
206
+ - Added cache control
207
+ - Converter from CSS to XPath replaced by faster
208
+
209
+ ### 1.0
210
+
211
+ - First release
vendor/imangazaliev/didom/LICENSE ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2015 Muhammad Imangazaliev
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is furnished
8
+ to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
vendor/imangazaliev/didom/README-RU.md ADDED
@@ -0,0 +1,833 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DiDOM
2
+
3
+ [![Build Status](https://codeship.com/projects/cf938980-36f0-0134-119e-36dc468776c7/status?branch=master)](https://codeship.com/projects/165662)
4
+ [![Total Downloads](https://poser.pugx.org/imangazaliev/didom/downloads)](https://packagist.org/packages/imangazaliev/didom)
5
+ [![Latest Stable Version](https://poser.pugx.org/imangazaliev/didom/v/stable)](https://packagist.org/packages/imangazaliev/didom)
6
+ [![License](https://poser.pugx.org/imangazaliev/didom/license)](https://packagist.org/packages/imangazaliev/didom)
7
+
8
+ [English version](README.md)
9
+
10
+ DiDOM - простая и быстрая библиотека для парсинга HTML.
11
+
12
+ ## Содержание
13
+
14
+ - [Установка](#Установка)
15
+ - [Быстрый старт](#Быстрый-старт)
16
+ - [Создание нового документа](#Создание-нового-документа)
17
+ - [Поиск элементов](#Поиск-элементов)
18
+ - [Проверка наличия элемента](#Проверка-наличия-элемента)
19
+ - [Подсчет количества элементов](#Подсчет-количества-элементов)
20
+ - [Поиск в элементе](#Поиск-в-элементе)
21
+ - [Поддерживамые селекторы](#Поддерживамые-селекторы)
22
+ - [Изменение содержимого](#Изменение-содержимого)
23
+ - [Вывод содержимого](#Вывод-содержимого)
24
+ - [Работа с элементами](#Работа-с-элементами)
25
+ - [Создание нового элемента](#Создание-нового-элемента)
26
+ - [Получение названия элемента](#Получение-названия-элемента)
27
+ - [Получение родительского элемента](#Получение-родительского-элемента)
28
+ - [Получение соседних элементов](#Получение-соседних-элементов)
29
+ - [Получение дочерних элементов](#Получение-соседних-элементов)
30
+ - [Получение документа](#Получение-документа)
31
+ - [Работа с атрибутами элемента](#Работа-с-атрибутами-элемента)
32
+ - [Сравнение элементов](#Сравнение-элементов)
33
+ - [Добавление дочерних элементов](#Добавление-дочерних-элементов)
34
+ - [Замена элемента](#Замена-элемента)
35
+ - [Удаление элемента](#Удаление-элемента)
36
+ - [Работа с кэшем](#Работа-с-кэшем)
37
+ - [Прочее](#Прочее)
38
+ - [Сравнение с другими парсерами](#Сравнение-с-другими-парсерами)
39
+
40
+ ## Установка
41
+
42
+ Для установки DiDOM выполните команду:
43
+
44
+ composer require imangazaliev/didom
45
+
46
+ ## Быстрый старт
47
+
48
+ ```php
49
+ use DiDom\Document;
50
+
51
+ $document = new Document('http://www.news.com/', true);
52
+
53
+ $posts = $document->find('.post');
54
+
55
+ foreach($posts as $post) {
56
+ echo $post->text(), "\n";
57
+ }
58
+ ```
59
+
60
+ ## Создание нового документа
61
+
62
+ DiDom позволяет загрузить HTML несколькими способами:
63
+
64
+ ##### Через конструктор
65
+
66
+ ```php
67
+ // в первом параметре передается строка с HTML
68
+ $document = new Document($html);
69
+
70
+ // путь к файлу
71
+ $document = new Document('page.html', true);
72
+
73
+ // или URL
74
+ $document = new Document('http://www.example.com/', true);
75
+
76
+ // также можно создать документ из DOMDocument
77
+ $domDocument = new DOMDocument();
78
+ $document = new Document($domDocument);
79
+ ```
80
+
81
+ Сигнатура:
82
+
83
+ ```php
84
+ __construct($string = null, $isFile = false, $encoding = 'UTF-8', $type = Document::TYPE_HTML)
85
+ ```
86
+
87
+ `$isFile` - указывает, что загружается файл. По умолчанию - `false`.
88
+
89
+ `$encoding` - кодировка документа. По умолчанию - UTF-8.
90
+
91
+ `$type` - тип документа (HTML - `Document::TYPE_HTML`, XML - `Document::TYPE_XML`). По умолчанию - `Document::TYPE_HTML`.
92
+
93
+ ##### Через отдельные методы
94
+
95
+ ```php
96
+ $document = new Document();
97
+
98
+ $document->loadHtml($html);
99
+
100
+ $document->loadHtmlFile('page.html');
101
+
102
+ $document->loadHtmlFile('http://www.example.com/');
103
+ ```
104
+
105
+ Для загрузки XML есть соответствующие методы `loadXml` и `loadXmlFile`.
106
+
107
+ При загрузке документа через эти методы, парсеру можно передать дополнительные [опции](http://php.net/manual/ru/libxml.constants.php):
108
+
109
+ ```php
110
+ $document->loadHtml($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
111
+ $document->loadHtmlFile($url, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
112
+
113
+ $document->loadXml($xml, LIBXML_PARSEHUGE);
114
+ $document->loadXmlFile($url, LIBXML_PARSEHUGE);
115
+ ```
116
+
117
+ ## Поиск элементов
118
+
119
+ В качестве выражения для поиска можно передать CSS-селектор или XPath. Для этого в первом параметре нужно передать само выражение, а во втором - его тип (по умолчанию - `Query::TYPE_CSS`):
120
+
121
+ ##### Через метод `find()`:
122
+
123
+ ```php
124
+ use DiDom\Document;
125
+ use DiDom\Query;
126
+
127
+ ...
128
+
129
+ // CSS-селектор
130
+ $posts = $document->find('.post');
131
+
132
+ // эквивалентно
133
+ $posts = $document->find('.post', Query::TYPE_CSS);
134
+
135
+ // XPath-выражение
136
+ $posts = $document->find("//div[contains(@class, 'post')]", Query::TYPE_XPATH);
137
+ ```
138
+
139
+ Метод вернет массив с элементами (экземпляры класса `DiDom\Element`) или пустой массив, если не найден ни один элемент, соответствующий выражению.
140
+
141
+ При желании можно получить массив узлов без преобразования в Element или текст (`DOMElement`/`DOMText`/`DOMComment`/`DOMAttr`, в зависимости от выражения), для этого необходимо передать в качестве третьего параметра `false`.
142
+
143
+ ##### Через метод `first()`:
144
+
145
+ Возвращает первый найденный элемент или `null`, если не найдено ни одного элемента.
146
+
147
+ Принимает те же параметры, что и метод `find()`.
148
+
149
+ ##### Через магический метод `__invoke()`:
150
+
151
+ ```php
152
+ $posts = $document('.post');
153
+ ```
154
+
155
+ Принимает те же параметры, что и метод `find()`.
156
+
157
+ **Внимание:** использование данного метода нежелательно, т.к. в будущем он может быть удален.
158
+
159
+ ##### Через метод `xpath()`:
160
+
161
+ ```php
162
+ $posts = $document->xpath("//*[contains(concat(' ', normalize-space(@class), ' '), ' post ')]");
163
+ ```
164
+
165
+ ## Проверка наличия элемента
166
+
167
+ Проверить наличие элемента можно с помощью метода `has()`:
168
+
169
+ ```php
170
+ if ($document->has('.post')) {
171
+ // код
172
+ }
173
+ ```
174
+
175
+ Если нужно проверить наличие элемента, а затем получить его, то можно сделать так:
176
+
177
+ ```php
178
+ if ($document->has('.post')) {
179
+ $elements = $document->find('.post');
180
+
181
+ // код
182
+ }
183
+ ```
184
+
185
+ но быстрее так:
186
+
187
+ ```php
188
+ $elements = $document->find('.post');
189
+
190
+ if (count($elements) > 0) {
191
+ // код
192
+ }
193
+ ```
194
+
195
+ т.к. в первом случае выполняется два запроса.
196
+
197
+ ## Подсчет количества элементов
198
+
199
+ Метод `count()` позволяет подсчитать количество дочерних элементов, соотвествующих селектору:
200
+
201
+ ```php
202
+ // выведет количество ссылок в документе
203
+ echo $document->count('a');
204
+ ```
205
+
206
+ ```php
207
+ // выведет количество пунктов в списке
208
+ echo $document->first('ul')->count('> li');
209
+ ```
210
+
211
+ ## Поиск в элементе
212
+
213
+ Методы `find()`, `first()`, `xpath()`, `has()`, `count()` доступны также и для элемента.
214
+
215
+ Пример:
216
+
217
+ ```php
218
+ echo $document->find('nav')[0]->first('ul.menu')->xpath('//li')[0]->text();
219
+ ```
220
+
221
+ #### Метод `findInDocument()`
222
+
223
+ При изменении, замене или удалении элемента, найденного в другом элементе, документ не будет изменен. Данное поведение связано с тем, что в методах `find()` и `first()` класса `Element` создается новый документ, в котором и производится поиск.
224
+
225
+ Для поиска элементов в исходном документе необходимо использовать методы `findInDocument()` и `firstInDocument()`:
226
+
227
+ ```php
228
+ // ничего не выйдет
229
+ $document->first('head')->first('title')->remove();
230
+
231
+ // а вот так да
232
+ $document->first('head')->firstInDocument('title')->remove();
233
+ ```
234
+
235
+ **Внимание:** метод `findInDocument()` работает только для элементов, которые принадлежат какому-либо документу, либо созданых через `new Element(...)`. Если элемент не принадлежит к какому-либо документу, будет выброшено исключение `LogicException`;
236
+
237
+ ## Поддерживамые селекторы
238
+
239
+ DiDom поддерживает поиск по:
240
+
241
+ - тэгу
242
+ - классу, идентификатору, имени и значению атрибута
243
+ - псевдоклассам:
244
+ - first-, last-, nth-child
245
+ - empty и not-empty
246
+ - contains
247
+ - has
248
+
249
+ ```php
250
+ // все ссылки
251
+ $document->find('a');
252
+
253
+ // любой элемент с id = "foo" и классом "bar"
254
+ $document->find('#foo.bar');
255
+
256
+ // любой элемент, у которого есть атрибут "name"
257
+ $document->find('[name]');
258
+
259
+ // эквивалентно
260
+ $document->find('*[name]');
261
+
262
+ // поле ввода с именем "foo"
263
+ $document->find('input[name=foo]');
264
+ $document->find('input[name=\'foo\']');
265
+ $document->find('input[name="foo"]');
266
+
267
+ // поле ввода с именем "foo" и значением "bar"
268
+ $document->find('input[name="foo"][value="bar"]');
269
+
270
+ // поле ввода, название которого НЕ равно "foo"
271
+ $document->find('input[name!="foo"]');
272
+
273
+ // любой элемент, у которого есть атрибут,
274
+ // начинающийся с "data-" и равный "foo"
275
+ $document->find('*[^data-=foo]');
276
+
277
+ // все ссылки, у которых адрес начинается с https
278
+ $document->find('a[href^=https]');
279
+
280
+ // все изображения с расширением png
281
+ $document->find('img[src$=png]');
282
+
283
+ // все ссылки, содержащие в своем адресе строку "example.com"
284
+ $document->find('a[href*=example.com]');
285
+
286
+ // все ссылки, содержащие в атрибуте data-foo значение bar отделенное пробелом
287
+ $document->find('a[data-foo~=bar]');
288
+
289
+ // текст всех ссылок с классом "foo" (массив строк)
290
+ $document->find('a.foo::text');
291
+
292
+ // эквивалентно
293
+ $document->find('a.foo::text()');
294
+
295
+ // адрес и текст подсказки всех полей с классом "bar"
296
+ $document->find('a.bar::attr(href|title)');
297
+
298
+ // все ссылки, которые являются прямыми потомками текущего элемента
299
+ $element->find('> a');
300
+ ```
301
+
302
+ ## Изменение содержимого
303
+
304
+ ### Изменение HTML
305
+
306
+ ```php
307
+ $element->setInnerHtml('<a href="#">Foo</a>');
308
+ ```
309
+
310
+ ### Изменение значения
311
+
312
+ ```php
313
+ $element->setValue('Foo');
314
+ ```
315
+
316
+ ## Вывод содержимого
317
+
318
+ ### Получение HTML
319
+
320
+ ##### Через метод `html()`:
321
+
322
+ ```php
323
+ // HTML-код документа
324
+ echo $document->html();
325
+
326
+ // HTML-код элемента
327
+ echo $document->first('.post')->html();
328
+ ```
329
+
330
+ ##### Приведение к строке:
331
+
332
+ ```php
333
+ // HTML-код документа
334
+ $html = (string) $document;
335
+
336
+ // HTML-код элемента
337
+ $html = (string) $document->first('.post');
338
+ ```
339
+
340
+ **Внимание:** использование данного способа нежелательно, т.к. в будущем он может быть удален.
341
+
342
+ ##### Форматирование HTML при выводе
343
+
344
+ ```php
345
+ echo $document->format()->html();
346
+ ```
347
+
348
+ Метод `format()` отсутствует у элемента, поэтому, если нужно получить отформатированный HTML-код элемента, необходимо сначала преобразовать его в документ:
349
+
350
+ ```php
351
+ $html = $element->toDocument()->format()->html();
352
+ ```
353
+
354
+ #### Внутренний HTML
355
+
356
+ ```php
357
+ $innerHtml = $element->innerHtml();
358
+ ```
359
+
360
+ Метод `innerHtml()` отсутствует у документа, поэтому, если нужно получить внутренний HTML-код документа, необходимо сначала преобразовать его в элемент:
361
+
362
+ ```php
363
+ $innerHtml = $document->toElement()->innerHtml();
364
+ ```
365
+
366
+ ### Получение XML
367
+
368
+ ```php
369
+ // XML-код документа
370
+ echo $document->xml();
371
+
372
+ // XML-код элемента
373
+ echo $document->first('book')->xml();
374
+ ```
375
+
376
+ ### Получение содержимого
377
+
378
+ Возвращает текстовое содержимое узла и его потомков:
379
+
380
+ ```php
381
+ echo $element->text();
382
+ ```
383
+
384
+ ## Создание нового элемента
385
+
386
+ ### Создание экземпляра класса
387
+
388
+ ```php
389
+ use DiDom\Element;
390
+
391
+ $element = new Element('span', 'Hello');
392
+
393
+ // выведет "<span>Hello</span>"
394
+ echo $element->html();
395
+ ```
396
+
397
+ Первым параметром передается название элемента, вторым - его значение (необязательно), третьим - атрибуты элемента (необязательно).
398
+
399
+ Пример создания элемента с атрибутами:
400
+
401
+ ```php
402
+ $attributes = ['name' => 'description', 'placeholder' => 'Enter description of item'];
403
+
404
+ $element = new Element('textarea', 'Text', $attributes);
405
+ ```
406
+
407
+ Элемент можно создать и из экземпляра класса `DOMElement`:
408
+
409
+ ```php
410
+ use DiDom\Element;
411
+ use DOMElement;
412
+
413
+ $domElement = new DOMElement('span', 'Hello');
414
+ $element = new Element($domElement);
415
+ ```
416
+
417
+ #### Изменение элемента, созданного из `DOMElement`
418
+
419
+ Экземпляры класса `DOMElement`, созданные через конструктор (`new DOMElement(...)`), являются неизменяемыми, поэтому и элементы (экземпляры класса `DiDom\Element`), созданные из таких объектов, так же являются неизменяемыми.
420
+
421
+ Пример:
422
+
423
+ ```php
424
+ $element = new Element('span', 'Hello');
425
+
426
+ // добавит атрибут "id" со значением "greeting"
427
+ $element->attr('id', 'greeting');
428
+
429
+ $domElement = new DOMElement('span', 'Hello');
430
+ $element = new Element($domElement);
431
+
432
+ // будет выброшено исключение
433
+ // DOMException with message 'No Modification Allowed Error'
434
+ $element->attr('id', 'greeting');
435
+ ```
436
+
437
+ ### С помощью метода `Document::createElement()`
438
+
439
+ ```php
440
+ $document = new Document($html);
441
+
442
+ $element = $document->createElement('span', 'Hello');
443
+ ```
444
+
445
+ ### С помощью CSS-селектора
446
+
447
+ Первый параметр - селектор, второй - значение, третий - массив с атрибутами.
448
+
449
+ Атрибуты элемента могут быть указаны как в селекторе, так и переданы отдельно в третьем параметре.
450
+
451
+ Если название атрибута в массиве совпадает с названием атрибута из селектора, будет использовано значение, указанное в селекторе.
452
+
453
+ ```php
454
+ $document = new Document($html);
455
+
456
+ $element = $document->createElementBySelector('div.block', 'Foo', [
457
+ 'id' => '#content',
458
+ 'class' => '.container',
459
+ ]);
460
+ ```
461
+
462
+ Можно так же использовать статический метод `createBySelector` класса `Element`:
463
+
464
+ ```php
465
+ $element = Element::createBySelector('div.block', 'Foo', [
466
+ 'id' => '#content',
467
+ 'class' => '.container',
468
+ ]);
469
+ ```
470
+
471
+ ## Получение названия элемента
472
+
473
+ ```php
474
+ $element->tag;
475
+ ```
476
+
477
+ ## Получение родительского элемента
478
+
479
+ ```php
480
+ $element->parent();
481
+ ```
482
+
483
+ Так же можно получить родительский элемент, соответствующий селектору:
484
+
485
+ ```php
486
+ $element->closest('.foo');
487
+ ```
488
+
489
+ Вернет родительский элемент, у которого есть класс `foo`. Если подходящий элемент не найден, метод вернет `null`.
490
+
491
+ ## Получение соседних элементов
492
+
493
+ Первый аргумент - CSS-селектор, второй - тип узла (`DOMElement`, `DOMText` или `DOMComment`).
494
+
495
+ Если оба аргумента опущены, будет осуществлен поиск узлов любого типа.
496
+
497
+ Если селектор указан, а тип узла нет, будет использован тип `DOMElement`.
498
+
499
+ **Внимание:** Селектор можно использовать только с типом `DOMElement`.
500
+
501
+ ```php
502
+ // предыдущий элемент
503
+ $item->previousSibling();
504
+
505
+ // предыдущий элемент, соответствующий селектору
506
+ $item->previousSibling('span');
507
+
508
+ // предыдущий элемент типа DOMElement
509
+ $item->previousSibling(null, 'DOMElement');
510
+
511
+ // предыдущий элемент типа DOMComment
512
+ $item->previousSibling(null, 'DOMComment');
513
+ ```
514
+
515
+ ```php
516
+ // все предыдущие элементы
517
+ $item->previousSiblings();
518
+
519
+ // все предыдущие элементы, соответствующие селектору
520
+ $item->previousSiblings('span');
521
+
522
+ // все предыдущие элементы типа DOMElement
523
+ $item->previousSiblings(null, 'DOMElement');
524
+
525
+ // все предыдущие элементы типа DOMComment
526
+ $item->previousSiblings(null, 'DOMComment');
527
+ ```
528
+
529
+ ```php
530
+ // следующий элемент
531
+ $item->nextSibling();
532
+
533
+ // следующий элемент, соответствующий селектору
534
+ $item->nextSibling('span');
535
+
536
+ // следующий элемент типа DOMElement
537
+ $item->nextSibling(null, 'DOMElement');
538
+
539
+ // следующий элемент типа DOMComment
540
+ $item->nextSibling(null, 'DOMComment');
541
+ ```
542
+
543
+ ```php
544
+ // все последующие элементы
545
+ $item->nextSiblings();
546
+
547
+ // все последующие элементы, соответствующие селектору
548
+ $item->nextSiblings('span');
549
+
550
+ // все последующие элементы типа DOMElement
551
+ $item->nextSiblings(null, 'DOMElement');
552
+
553
+ // все последующие элементы типа DOMComment
554
+ $item->nextSiblings(null, 'DOMComment');
555
+ ```
556
+
557
+ ## Получение дочерних элементов
558
+
559
+ ```php
560
+ $html = '<div>Foo<span>Bar</span><!--Baz--></div>';
561
+
562
+ $document = new Document($html);
563
+
564
+ $div = $document->first('div');
565
+
566
+ // элемент (DOMElement)
567
+ // string(3) "Bar"
568
+ var_dump($div->child(1)->text());
569
+
570
+ // текстовый узел (DOMText)
571
+ // string(3) "Foo"
572
+ var_dump($div->firstChild()->text());
573
+
574
+ // комментарий (DOMComment)
575
+ // string(3) "Baz"
576
+ var_dump($div->lastChild()->text());
577
+
578
+ // array(3) { ... }
579
+ var_dump($div->children());
580
+ ```
581
+
582
+ ## Получение документа
583
+
584
+ ```php
585
+ $document = new Document($html);
586
+
587
+ $element = $document->first('input[name=email]');
588
+
589
+ $document2 = $element->getDocument();
590
+
591
+ // bool(true)
592
+ var_dump($document->is($document2));
593
+ ```
594
+
595
+ ## Работа с атрибутами элемента
596
+
597
+ #### Создание/изменение атрибута
598
+
599
+ ##### Через метод `setAttribute`:
600
+ ```php
601
+ $element->setAttribute('name', 'username');
602
+ ```
603
+
604
+ ##### Через метод `attr`:
605
+ ```php
606
+ $element->attr('name', 'username');
607
+ ```
608
+
609
+ ##### Через магический метод `__set`:
610
+ ```php
611
+ $element->name = 'username';
612
+ ```
613
+
614
+ #### Получение значения атрибута
615
+
616
+ ##### Через метод `getAttribute`:
617
+ ```php
618
+ $username = $element->getAttribute('value');
619
+ ```
620
+
621
+ ##### Через метод `attr`:
622
+ ```php
623
+ $username = $element->attr('value');
624
+ ```
625
+
626
+ ##### Через магический метод `__get`:
627
+ ```php
628
+ $username = $element->name;
629
+ ```
630
+
631
+ Если атрибут не найден, вернет `null`.
632
+
633
+ #### Проверка наличия атрибута
634
+
635
+ ##### Через метод `hasAttribute`:
636
+ ```php
637
+ if ($element->hasAttribute('name')) {
638
+ // код
639
+ }
640
+ ```
641
+
642
+ ##### Через магический метод `__isset`:
643
+ ```php
644
+ if (isset($element->name)) {
645
+ // код
646
+ }
647
+ ```
648
+
649
+ #### Удаление атрибута:
650
+
651
+ ##### Через метод `removeAttribute`:
652
+ ```php
653
+ $element->removeAttribute('name');
654
+ ```
655
+
656
+ ##### Через магический метод `__unset`:
657
+ ```php
658
+ unset($element->name);
659
+ ```
660
+
661
+ #### Получение всех атрибутов:
662
+
663
+ ```php
664
+ var_dump($element->attributes());
665
+ ```
666
+
667
+ #### Получение определенных атрибутов:
668
+
669
+ ```php
670
+ var_dump($element->attributes(['name', 'type']));
671
+ ```
672
+
673
+ #### Удаление всех атрибутов:
674
+
675
+ ```php
676
+ $element->removeAllAttributes();
677
+ ```
678
+
679
+ #### Удаление всех атрибутов, за исключением указанных:
680
+
681
+ ```php
682
+ $element->removeAllAttributes(['name', 'type']);
683
+ ```
684
+
685
+ ## Сравнение элементов
686
+
687
+ ```php
688
+ $element = new Element('span', 'hello');
689
+ $element2 = new Element('span', 'hello');
690
+
691
+ // bool(true)
692
+ var_dump($element->is($element));
693
+
694
+ // bool(false)
695
+ var_dump($element->is($element2));
696
+ ```
697
+
698
+ ## Добавление дочерних элементов
699
+
700
+ ```php
701
+ $list = new Element('ul');
702
+
703
+ $item = new Element('li', 'Item 1');
704
+
705
+ $list->appendChild($item);
706
+
707
+ $items = [
708
+ new Element('li', 'Item 2'),
709
+ new Element('li', 'Item 3'),
710
+ ];
711
+
712
+ $list->appendChild($items);
713
+ ```
714
+
715
+ ## Замена элемента
716
+
717
+ ```php
718
+ $title = new Element('title', 'foo');
719
+
720
+ $document->first('title')->replace($title);
721
+ ```
722
+
723
+ **Внимание:** заменить можно только те элементы, которые были найдены непосредственно в документе:
724
+
725
+ ```php
726
+ // ничего не выйдет
727
+ $document->first('head')->first('title')->replace($title);
728
+
729
+ // а вот так да
730
+ $document->first('head title')->replace($title);
731
+ ```
732
+
733
+ Подробнее об этом в разделе [Поиск в элементе](#Поиск-в-элементе).
734
+
735
+ ## Удаление элемента
736
+
737
+ ```php
738
+ $document->first('title')->remove();
739
+ ```
740
+
741
+ **Внимание:** удалить можно только те элементы, которые были найдены непосредственно в документе:
742
+
743
+ ```php
744
+ // ничего не выйдет
745
+ $document->first('head')->first('title')->remove();
746
+
747
+ // а вот так да
748
+ $document->first('head title')->remove();
749
+ ```
750
+
751
+ Подробнее об этом в разделе [Поиск в элементе](#Поиск-в-элементе).
752
+
753
+ ## Работа с кэшем
754
+
755
+ Кэш - массив XPath-выражений, полученных из CSS.
756
+
757
+ #### Получение кэша
758
+
759
+ ```php
760
+ use DiDom\Query;
761
+
762
+ ...
763
+
764
+ $xpath = Query::compile('h2');
765
+ $compiled = Query::getCompiled();
766
+
767
+ // array('h2' => '//h2')
768
+ var_dump($compiled);
769
+ ```
770
+
771
+ #### Установка кэша
772
+
773
+ ```php
774
+ Query::setCompiled(['h2' => '//h2']);
775
+ ```
776
+
777
+ ## Прочее
778
+
779
+ #### `preserveWhiteSpace`
780
+
781
+ По умолчанию сохранение пробелов между тегами отключено.
782
+
783
+ Включать опцию `preserveWhiteSpace` следует до загрузки документа:
784
+
785
+ ```php
786
+ $document = new Document();
787
+
788
+ $document->preserveWhiteSpace();
789
+
790
+ $document->loadXml($xml);
791
+ ```
792
+
793
+ #### `matches`
794
+
795
+ Возвращает `true`, если элемент соответсвует селектору:
796
+
797
+ ```php
798
+ // вернет true, если элемент это div с идентификатором content
799
+ $element->matches('div#content');
800
+
801
+ // строгое соответствие
802
+ // вернет true, если элемент это div с идентификатором content и ничего более
803
+ // если у элемента будут какие-либо другие атрибуты, метод вернет false
804
+ $element->matches('div#content', true);
805
+ ```
806
+
807
+ #### `isElementNode`
808
+
809
+ Проверяет, является ли элемент узлом типа DOMElement:
810
+
811
+ ```php
812
+ $element->isElementNode();
813
+ ```
814
+
815
+ #### `isTextNode`
816
+
817
+ Проверяет, является ли элемент текстовым узлом (DOMText):
818
+
819
+ ```php
820
+ $element->isTextNode();
821
+ ```
822
+
823
+ #### `isCommentNode`
824
+
825
+ Проверяет, является ли элемент комментарием (DOMComment):
826
+
827
+ ```php
828
+ $element->isCommentNode();
829
+ ```
830
+
831
+ ## Сравнение с другими парсерами
832
+
833
+ [Сравнение с другими парсерами](https://github.com/Imangazaliev/DiDOM/wiki/Сравнение-с-другими-парсерами-(1.6.3))
vendor/imangazaliev/didom/README.md ADDED
@@ -0,0 +1,603 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DiDOM
2
+
3
+ [![Build Status](https://codeship.com/projects/cf938980-36f0-0134-119e-36dc468776c7/status?branch=master)](https://codeship.com/projects/165662)
4
+ [![Total Downloads](https://poser.pugx.org/imangazaliev/didom/downloads)](https://packagist.org/packages/imangazaliev/didom)
5
+ [![Latest Stable Version](https://poser.pugx.org/imangazaliev/didom/v/stable)](https://packagist.org/packages/imangazaliev/didom)
6
+ [![License](https://poser.pugx.org/imangazaliev/didom/license)](https://packagist.org/packages/imangazaliev/didom)
7
+
8
+ [README на русском](README-RU.md)
9
+
10
+ DiDOM - simple and fast HTML parser.
11
+
12
+ ## Contents
13
+
14
+ - [Installation](#installation)
15
+ - [Quick start](#quick-start)
16
+ - [Creating new document](#creating-new-document)
17
+ - [Search for elements](#search-for-elements)
18
+ - [Verify if element exists](#verify-if-element-exists)
19
+ - [Supported selectors](#supported-selectors)
20
+ - [Output](#output)
21
+ - [Creating a new element](#creating-a-new-element)
22
+ - [Getting the name of an element](#getting-the-name-of-an-element)
23
+ - [Getting parent element](#getting-parent-element)
24
+ - [Getting sibling elements](#getting-sibling-elements)
25
+ - [Getting the child elements](#getting-the-child-elements)
26
+ - [Getting document](#getting-document)
27
+ - [Working with element attributes](#working-with-element-attributes)
28
+ - [Comparing elements](#comparing-elements)
29
+ - [Adding a child element](#adding-a-child-element)
30
+ - [Replacing element](#replacing-element)
31
+ - [Removing element](#removing-element)
32
+ - [Working with cache](#working-with-cache)
33
+ - [Miscellaneous](#miscellaneous)
34
+ - [Comparison with other parsers](#comparison-with-other-parsers)
35
+
36
+ ## Installation
37
+
38
+ To install DiDOM run the command:
39
+
40
+ composer require imangazaliev/didom
41
+
42
+ ## Quick start
43
+
44
+ ```php
45
+ use DiDom\Document;
46
+
47
+ $document = new Document('http://www.news.com/', true);
48
+
49
+ $posts = $document->find('.post');
50
+
51
+ foreach($posts as $post) {
52
+ echo $post->text(), "\n";
53
+ }
54
+ ```
55
+
56
+ ## Creating new document
57
+
58
+ DiDom allows to load HTML in several ways:
59
+
60
+ ##### With constructor
61
+
62
+ ```php
63
+ // the first parameter is a string with HTML
64
+ $document = new Document($html);
65
+
66
+ // file path
67
+ $document = new Document('page.html', true);
68
+
69
+ // or URL
70
+ $document = new Document('http://www.example.com/', true);
71
+ ```
72
+
73
+ The second parameter specifies if you need to load file. Default is `false`.
74
+
75
+ ##### With separate methods
76
+
77
+ ```php
78
+ $document = new Document();
79
+
80
+ $document->loadHtml($html);
81
+
82
+ $document->loadHtmlFile('page.html');
83
+
84
+ $document->loadHtmlFile('http://www.example.com/');
85
+ ```
86
+
87
+ There are two methods available for loading XML: `loadXml` and `loadXmlFile`.
88
+
89
+ These methods accept additional options:
90
+
91
+ ```php
92
+ $document->loadHtml($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
93
+ ```
94
+
95
+ ## Search for elements
96
+
97
+ DiDOM accepts CSS selector or XPath as an expression for search. You need to path expression as the first parameter, and specify its type in the second one (default type is `Query::TYPE_CSS`):
98
+
99
+ ##### With method `find()`:
100
+
101
+ ```php
102
+ use DiDom\Document;
103
+ use DiDom\Query;
104
+
105
+ ...
106
+
107
+ // CSS selector
108
+ $posts = $document->find('.post');
109
+
110
+ // XPath
111
+ $posts = $document->find("//div[contains(@class, 'post')]", Query::TYPE_XPATH);
112
+ ```
113
+
114
+ If the elements that match a given expression are found, then method returns an array of instances of `DiDom\Element`, otherwise - an empty array. You could also get an array of `DOMElement` objects. To get this, pass `false` as the third parameter.
115
+
116
+ ##### With magic method `__invoke()`:
117
+
118
+ ```php
119
+ $posts = $document('.post');
120
+ ```
121
+
122
+ ##### With method `xpath()`:
123
+
124
+ ```php
125
+ $posts = $document->xpath("//*[contains(concat(' ', normalize-space(@class), ' '), ' post ')]");
126
+ ```
127
+
128
+ You can do search inside an element:
129
+
130
+ ```php
131
+ echo $document->find('nav')[0]->first('ul.menu')->xpath('//li')[0]->text();
132
+ ```
133
+
134
+ ### Verify if element exists
135
+
136
+ To verify if element exist use `has()` method:
137
+
138
+ ```php
139
+ if ($document->has('.post')) {
140
+ // code
141
+ }
142
+ ```
143
+
144
+ If you need to check if element exist and then get it:
145
+
146
+ ```php
147
+ if ($document->has('.post')) {
148
+ $elements = $document->find('.post');
149
+ // code
150
+ }
151
+ ```
152
+
153
+ but it would be faster like this:
154
+
155
+ ```php
156
+ if (count($elements = $document->find('.post')) > 0) {
157
+ // code
158
+ }
159
+ ```
160
+
161
+ because in the first case it makes two requests.
162
+
163
+ ## Supported selectors
164
+
165
+ DiDom supports search by:
166
+
167
+ - tag
168
+ - class, ID, name and value of an attribute
169
+ - pseudo-classes:
170
+ - first-, last-, nth-child
171
+ - empty and not-empty
172
+ - contains
173
+ - has
174
+
175
+ ```php
176
+ // all links
177
+ $document->find('a');
178
+
179
+ // any element with id = "foo" and "bar" class
180
+ $document->find('#foo.bar');
181
+
182
+ // any element with attribute "name"
183
+ $document->find('[name]');
184
+ // the same as
185
+ $document->find('*[name]');
186
+
187
+ // input field with the name "foo"
188
+ $document->find('input[name=foo]');
189
+ $document->find('input[name=\'bar\']');
190
+ $document->find('input[name="baz"]');
191
+
192
+ // any element that has an attribute starting with "data-" and the value "foo"
193
+ $document->find('*[^data-=foo]');
194
+
195
+ // all links starting with https
196
+ $document->find('a[href^=https]');
197
+
198
+ // all images with the extension png
199
+ $document->find('img[src$=png]');
200
+
201
+ // all links containing the string "example.com"
202
+ $document->find('a[href*=example.com]');
203
+
204
+ // text of the links with "foo" class
205
+ $document->find('a.foo::text');
206
+
207
+ // address and title of all the fields with "bar" class
208
+ $document->find('a.bar::attr(href|title)');
209
+ ```
210
+
211
+ ## Output
212
+
213
+ ### Getting HTML
214
+
215
+ ##### With method `html()`:
216
+
217
+ ```php
218
+ $posts = $document->find('.post');
219
+
220
+ echo $posts[0]->html();
221
+ ```
222
+
223
+ ##### Casting to string:
224
+
225
+ ```php
226
+ $html = (string) $posts[0];
227
+ ```
228
+
229
+ ##### Formatting HTML output
230
+
231
+ ```php
232
+ $html = $document->format()->html();
233
+ ```
234
+
235
+ An element does not have `format()` method, so if you need to output formatted HTML of the element, then first you have to convert it to a document:
236
+
237
+
238
+ ```php
239
+ $html = $element->toDocument()->format()->html();
240
+ ```
241
+
242
+ #### Inner HTML
243
+
244
+ ```php
245
+ $innerHtml = $element->innerHtml();
246
+ ```
247
+
248
+ Document does not have the method `innerHtml()`, therefore, if you need to get inner HTML of a document, convert it into an element first:
249
+
250
+ ```php
251
+ $innerHtml = $document->toElement()->innerHtml();
252
+ ```
253
+
254
+ ### Getting XML
255
+
256
+ ```php
257
+ echo $document->xml();
258
+
259
+ echo $document->first('book')->xml();
260
+ ```
261
+
262
+ ### Getting content
263
+
264
+ ```php
265
+ $posts = $document->find('.post');
266
+
267
+ echo $posts[0]->text();
268
+ ```
269
+
270
+ ## Creating a new element
271
+
272
+ ### Creating an instance of the class
273
+
274
+ ```php
275
+ use DiDom\Element;
276
+
277
+ $element = new Element('span', 'Hello');
278
+
279
+ // Outputs "<span>Hello</span>"
280
+ echo $element->html();
281
+ ```
282
+
283
+ First parameter is a name of an attribute, the second one is its value (optional), the third one is element attributes (optional).
284
+
285
+ An example of creating an element with attributes:
286
+
287
+ ```php
288
+ $attributes = ['name' => 'description', 'placeholder' => 'Enter description of item'];
289
+
290
+ $element = new Element('textarea', 'Text', $attributes);
291
+ ```
292
+
293
+ An element can be created from an instance of the class `DOMElement`:
294
+
295
+ ```php
296
+ use DiDom\Element;
297
+ use DOMElement;
298
+
299
+ $domElement = new DOMElement('span', 'Hello');
300
+
301
+ $element = new Element($domElement);
302
+ ```
303
+
304
+ ### Using the method `createElement`
305
+
306
+ ```php
307
+ $document = new Document($html);
308
+
309
+ $element = $document->createElement('span', 'Hello');
310
+ ```
311
+
312
+ ## Getting the name of an element
313
+
314
+ ```php
315
+ $element->tag;
316
+ ```
317
+
318
+ ## Getting parent element
319
+
320
+ ```php
321
+ $document = new Document($html);
322
+
323
+ $input = $document->find('input[name=email]')[0];
324
+
325
+ var_dump($input->parent());
326
+ ```
327
+
328
+ ## Getting sibling elements
329
+
330
+ ```php
331
+ $document = new Document($html);
332
+
333
+ $item = $document->find('ul.menu > li')[1];
334
+
335
+ var_dump($item->previousSibling());
336
+
337
+ var_dump($item->nextSibling());
338
+ ```
339
+
340
+ ## Getting the child elements
341
+
342
+ ```php
343
+ $html = '<div>Foo<span>Bar</span><!--Baz--></div>';
344
+
345
+ $document = new Document($html);
346
+
347
+ $div = $document->first('div');
348
+
349
+ // element node (DOMElement)
350
+ // string(3) "Bar"
351
+ var_dump($div->child(1)->text());
352
+
353
+ // text node (DOMText)
354
+ // string(3) "Foo"
355
+ var_dump($div->firstChild()->text());
356
+
357
+ // comment node (DOMComment)
358
+ // string(3) "Baz"
359
+ var_dump($div->lastChild()->text());
360
+
361
+ // array(3) { ... }
362
+ var_dump($div->children());
363
+ ```
364
+
365
+ ## Getting document
366
+
367
+ ```php
368
+ $document = new Document($html);
369
+
370
+ $element = $document->find('input[name=email]')[0];
371
+
372
+ $document2 = $element->getDocument();
373
+
374
+ // bool(true)
375
+ var_dump($document->is($document2));
376
+ ```
377
+
378
+ ## Working with element attributes
379
+
380
+ #### Creating/updating an attribute
381
+
382
+ ##### With method `setAttribute`:
383
+ ```php
384
+ $element->setAttribute('name', 'username');
385
+ ```
386
+
387
+ ##### With method `attr`:
388
+ ```php
389
+ $element->attr('name', 'username');
390
+ ```
391
+
392
+ ##### With magic method `__set`:
393
+ ```php
394
+ $element->name = 'username';
395
+ ```
396
+
397
+ #### Getting value of an attribute
398
+
399
+ ##### With method `getAttribute`:
400
+
401
+ ```php
402
+ $username = $element->getAttribute('value');
403
+ ```
404
+
405
+ ##### With method `attr`:
406
+
407
+ ```php
408
+ $username = $element->attr('value');
409
+ ```
410
+
411
+ ##### With magic method `__get`:
412
+
413
+ ```php
414
+ $username = $element->name;
415
+ ```
416
+
417
+ Returns `null` if attribute is not found.
418
+
419
+ #### Verify if attribute exists
420
+
421
+ ##### With method `hasAttribute`:
422
+
423
+ ```php
424
+ if ($element->hasAttribute('name')) {
425
+ // code
426
+ }
427
+ ```
428
+
429
+ ##### With magic method `__isset`:
430
+
431
+ ```php
432
+ if (isset($element->name)) {
433
+ // code
434
+ }
435
+ ```
436
+
437
+ #### Removing attribute:
438
+
439
+ ##### With method `removeAttribute`:
440
+
441
+ ```php
442
+ $element->removeAttribute('name');
443
+ ```
444
+
445
+ ##### With magic method `__unset`:
446
+
447
+ ```php
448
+ unset($element->name);
449
+ ```
450
+
451
+ ## Comparing elements
452
+
453
+ ```php
454
+ $element = new Element('span', 'hello');
455
+ $element2 = new Element('span', 'hello');
456
+
457
+ // bool(true)
458
+ var_dump($element->is($element));
459
+
460
+ // bool(false)
461
+ var_dump($element->is($element2));
462
+ ```
463
+
464
+ ## Appending child elements
465
+
466
+ ```php
467
+ $list = new Element('ul');
468
+
469
+ $item = new Element('li', 'Item 1');
470
+
471
+ $list->appendChild($item);
472
+
473
+ $items = [
474
+ new Element('li', 'Item 2'),
475
+ new Element('li', 'Item 3'),
476
+ ];
477
+
478
+ $list->appendChild($items);
479
+ ```
480
+
481
+ ## Adding a child element
482
+
483
+ ```php
484
+ $list = new Element('ul');
485
+
486
+ $item = new Element('li', 'Item 1');
487
+ $items = [
488
+ new Element('li', 'Item 2'),
489
+ new Element('li', 'Item 3'),
490
+ ];
491
+
492
+ $list->appendChild($item);
493
+ $list->appendChild($items);
494
+ ```
495
+
496
+ ## Replacing element
497
+
498
+ ```php
499
+ $element = new Element('span', 'hello');
500
+
501
+ $document->find('.post')[0]->replace($element);
502
+ ```
503
+
504
+ ## Removing element
505
+
506
+ ```php
507
+ $document->find('.post')[0]->remove();
508
+ ```
509
+
510
+ ## Working with cache
511
+
512
+ Cache is an array of XPath expressions, that were converted from CSS.
513
+
514
+ #### Getting from cache
515
+
516
+ ```php
517
+ use DiDom\Query;
518
+
519
+ ...
520
+
521
+ $xpath = Query::compile('h2');
522
+ $compiled = Query::getCompiled();
523
+
524
+ // array('h2' => '//h2')
525
+ var_dump($compiled);
526
+ ```
527
+
528
+ #### Cache setting
529
+
530
+ ```php
531
+ Query::setCompiled(['h2' => '//h2']);
532
+ ```
533
+
534
+ ## Miscellaneous
535
+
536
+ #### `preserveWhiteSpace`
537
+
538
+ By default, whitespace preserving is disabled.
539
+
540
+ You can enable the `preserveWhiteSpace` option before loading the document:
541
+
542
+ ```php
543
+ $document = new Document();
544
+
545
+ $document->preserveWhiteSpace();
546
+
547
+ $document->loadXml($xml);
548
+ ```
549
+
550
+ #### `count`
551
+
552
+ The `count ()` method counts children that match the selector:
553
+
554
+ ```php
555
+ // prints the number of links in the document
556
+ echo $document->count('a');
557
+ ```
558
+
559
+ ```php
560
+ // prints the number of items in the list
561
+ echo $document->first('ul')->count('li');
562
+ ```
563
+
564
+ #### `matches`
565
+
566
+ Returns `true` if the node matches the selector:
567
+
568
+ ```php
569
+ $element->matches('div#content');
570
+
571
+ // strict match
572
+ // returns true if the element is a div with id equals content and nothing else
573
+ // if the element has any other attributes the method returns false
574
+ $element->matches('div#content', true);
575
+ ```
576
+
577
+ #### `isElementNode`
578
+
579
+ Checks whether an element is an element (DOMElement):
580
+
581
+ ```php
582
+ $element->isElementNode();
583
+ ```
584
+
585
+ #### `isTextNode`
586
+
587
+ Checks whether an element is a text node (DOMText):
588
+
589
+ ```php
590
+ $element->isTextNode();
591
+ ```
592
+
593
+ #### `isCommentNode`
594
+
595
+ Checks whether the element is a comment (DOMComment):
596
+
597
+ ```php
598
+ $element->isCommentNode();
599
+ ```
600
+
601
+ ## Comparison with other parsers
602
+
603
+ [Comparison with other parsers](https://github.com/Imangazaliev/DiDOM/wiki/Comparison-with-other-parsers-(1.0))
vendor/imangazaliev/didom/composer.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "imangazaliev/didom",
3
+ "description": "Simple and fast HTML parser",
4
+ "type": "library",
5
+ "keywords": ["didom", "parser", "html", "xml"],
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/Imangazaliev/DiDOM",
8
+ "authors": [
9
+ {
10
+ "name": "Imangazaliev Muhammad",
11
+ "email": "imangazalievm@gmail.com"
12
+ }
13
+ ],
14
+ "require": {
15
+ "php": ">=5.4"
16
+ },
17
+ "require-dev": {
18
+ "phpunit/phpunit": "^4.8"
19
+ },
20
+ "autoload": {
21
+ "psr-4": {
22
+ "DiDom\\": "src/DiDom/"
23
+ }
24
+ },
25
+ "autoload-dev": {
26
+ "psr-4": {
27
+ "Tests\\": "tests/"
28
+ }
29
+ }
30
+ }
vendor/imangazaliev/didom/phpunit.xml ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <phpunit backupGlobals="false"
3
+ backupStaticAttributes="false"
4
+ bootstrap="tests/bootstrap.php"
5
+ colors="true"
6
+ convertErrorsToExceptions="true"
7
+ convertNoticesToExceptions="true"
8
+ convertWarningsToExceptions="true"
9
+ processIsolation="false"
10
+ stopOnFailure="false"
11
+ syntaxCheck="false">
12
+ <testsuites>
13
+ <testsuite name="DiDom Test Suite">
14
+ <directory>./tests/</directory>
15
+ </testsuite>
16
+ </testsuites>
17
+ </phpunit>
vendor/imangazaliev/didom/src/DiDom/ClassAttribute.php ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ use InvalidArgumentException;
6
+
7
+ class ClassAttribute
8
+ {
9
+ /**
10
+ * The DOM element instance.
11
+ *
12
+ * @var \DiDom\Element
13
+ */
14
+ protected $element;
15
+
16
+ /**
17
+ * @var string
18
+ */
19
+ protected $classesString = '';
20
+
21
+ /**
22
+ * @var string[]
23
+ */
24
+ protected $classes = [];
25
+
26
+ /**
27
+ * Constructor.
28
+ *
29
+ * @param \DiDom\Element $element
30
+ *
31
+ * @throws \InvalidArgumentException if passed argument is not an element node
32
+ */
33
+ public function __construct(Element $element)
34
+ {
35
+ if (!$element->isElementNode()) {
36
+ throw new InvalidArgumentException(sprintf('The element should contain DOMElement node'));
37
+ }
38
+
39
+ $this->element = $element;
40
+
41
+ $this->parseClassAttribute();
42
+ }
43
+
44
+ /**
45
+ * Parses class attribute of the element.
46
+ */
47
+ protected function parseClassAttribute()
48
+ {
49
+ if (!$this->element->hasAttribute('class')) {
50
+ // possible if class attribute has been removed
51
+ if ($this->classesString !== '') {
52
+ $this->classesString = '';
53
+ $this->classes = [];
54
+ }
55
+
56
+ return;
57
+ }
58
+
59
+ // if class attribute is not changed
60
+ if ($this->element->getAttribute('class') === $this->classesString) {
61
+ return;
62
+ }
63
+
64
+ // save class attribute as is (without trimming)
65
+ $this->classesString = $this->element->getAttribute('class');
66
+
67
+ $classesString = trim($this->classesString);
68
+
69
+ if ($classesString === '') {
70
+ $this->classes = [];
71
+
72
+ return;
73
+ }
74
+
75
+ $classes = explode(' ', $classesString);
76
+
77
+ $classes = array_map('trim', $classes);
78
+ $classes = array_filter($classes);
79
+ $classes = array_unique($classes);
80
+
81
+ $this->classes = array_values($classes);
82
+ }
83
+
84
+ /**
85
+ * Updates class attribute of the element.
86
+ */
87
+ protected function updateClassAttribute()
88
+ {
89
+ $this->classesString = implode(' ', $this->classes);
90
+
91
+ $this->element->setAttribute('class', $this->classesString);
92
+ }
93
+
94
+ /**
95
+ * @param string $className
96
+ *
97
+ * @return \DiDom\ClassAttribute
98
+ *
99
+ * @throws \InvalidArgumentException if class name is not a string
100
+ */
101
+ public function add($className)
102
+ {
103
+ if (!is_string($className)) {
104
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($className) ? get_class($className) : gettype($className))));
105
+ }
106
+
107
+ $this->parseClassAttribute();
108
+
109
+ if (in_array($className, $this->classes)) {
110
+ return $this;
111
+ }
112
+
113
+ $this->classes[] = $className;
114
+
115
+ $this->updateClassAttribute();
116
+
117
+ return $this;
118
+ }
119
+
120
+ /**
121
+ * @param array $classNames
122
+ *
123
+ * @return \DiDom\ClassAttribute
124
+ *
125
+ * @throws \InvalidArgumentException if class name is not a string
126
+ */
127
+ public function addMultiple(array $classNames)
128
+ {
129
+ $this->parseClassAttribute();
130
+
131
+ foreach ($classNames as $className) {
132
+ if (!is_string($className)) {
133
+ throw new InvalidArgumentException(sprintf('Class name must be a string, %s given', (is_object($className) ? get_class($className) : gettype($className))));
134
+ }
135
+
136
+ if (in_array($className, $this->classes)) {
137
+ continue;
138
+ }
139
+
140
+ $this->classes[] = $className;
141
+ }
142
+
143
+ $this->updateClassAttribute();
144
+
145
+ return $this;
146
+ }
147
+
148
+ /**
149
+ * @return string[]
150
+ */
151
+ public function getAll()
152
+ {
153
+ $this->parseClassAttribute();
154
+
155
+ return $this->classes;
156
+ }
157
+
158
+ /**
159
+ * @param string $className
160
+ *
161
+ * @return bool
162
+ */
163
+ public function contains($className)
164
+ {
165
+ if (!is_string($className)) {
166
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($className) ? get_class($className) : gettype($className))));
167
+ }
168
+
169
+ $this->parseClassAttribute();
170
+
171
+ return in_array($className, $this->classes);
172
+ }
173
+
174
+ /**
175
+ * @param string $className
176
+ *
177
+ * @return \DiDom\ClassAttribute
178
+ *
179
+ * @throws \InvalidArgumentException if class name is not a string
180
+ */
181
+ public function remove($className)
182
+ {
183
+ if (!is_string($className)) {
184
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($className) ? get_class($className) : gettype($className))));
185
+ }
186
+
187
+ $this->parseClassAttribute();
188
+
189
+ $classIndex = array_search($className, $this->classes);
190
+
191
+ if ($classIndex === false) {
192
+ return $this;
193
+ }
194
+
195
+ unset($this->classes[$classIndex]);
196
+
197
+ $this->updateClassAttribute();
198
+
199
+ return $this;
200
+ }
201
+
202
+ /**
203
+ * @param array $classNames
204
+ *
205
+ * @return \DiDom\ClassAttribute
206
+ *
207
+ * @throws \InvalidArgumentException if class name is not a string
208
+ */
209
+ public function removeMultiple(array $classNames)
210
+ {
211
+ $this->parseClassAttribute();
212
+
213
+ foreach ($classNames as $className) {
214
+ if (!is_string($className)) {
215
+ throw new InvalidArgumentException(sprintf('Class name must be a string, %s given', (is_object($className) ? get_class($className) : gettype($className))));
216
+ }
217
+
218
+ $classIndex = array_search($className, $this->classes);
219
+
220
+ if ($classIndex === false) {
221
+ continue;
222
+ }
223
+
224
+ unset($this->classes[$classIndex]);
225
+ }
226
+
227
+ $this->updateClassAttribute();
228
+
229
+ return $this;
230
+ }
231
+
232
+ /**
233
+ * @param string[] $exclusions
234
+ *
235
+ * @return \DiDom\ClassAttribute
236
+ */
237
+ public function removeAll(array $exclusions = [])
238
+ {
239
+ $this->parseClassAttribute();
240
+
241
+ $preservedClasses = [];
242
+
243
+ foreach ($exclusions as $className) {
244
+ if (!is_string($className)) {
245
+ throw new InvalidArgumentException(sprintf('Class name must be a string, %s given', (is_object($className) ? get_class($className) : gettype($className))));
246
+ }
247
+
248
+ if (!in_array($className, $this->classes)) {
249
+ continue;
250
+ }
251
+
252
+ $preservedClasses[] = $className;
253
+ }
254
+
255
+ $this->classes = $preservedClasses;
256
+
257
+ $this->updateClassAttribute();
258
+
259
+ return $this;
260
+ }
261
+
262
+ /**
263
+ * @return \DiDom\Element
264
+ */
265
+ public function getElement()
266
+ {
267
+ return $this->element;
268
+ }
269
+ }
vendor/imangazaliev/didom/src/DiDom/Document.php ADDED
@@ -0,0 +1,663 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ use DOMDocument;
6
+ use DOMXPath;
7
+ use InvalidArgumentException;
8
+ use RuntimeException;
9
+
10
+ class Document
11
+ {
12
+ /**
13
+ * Types of document.
14
+ *
15
+ * @const string
16
+ */
17
+ const TYPE_HTML = 'html';
18
+ const TYPE_XML = 'xml';
19
+
20
+ /**
21
+ * @var \DOMDocument
22
+ */
23
+ protected $document;
24
+
25
+ /**
26
+ * @var string
27
+ */
28
+ protected $type;
29
+
30
+ /**
31
+ * @var string
32
+ */
33
+ protected $encoding;
34
+
35
+ /**
36
+ * Constructor.
37
+ *
38
+ * @param string|null $string HTML or XML string or file path
39
+ * @param bool $isFile Indicates that in first parameter was passed to the file path
40
+ * @param string $encoding The document encoding
41
+ * @param string $type The document type
42
+ *
43
+ * @throws \InvalidArgumentException if the passed encoding is not a string
44
+ */
45
+ public function __construct($string = null, $isFile = false, $encoding = 'UTF-8', $type = Document::TYPE_HTML)
46
+ {
47
+ if ($string instanceof DOMDocument) {
48
+ $this->document = $string;
49
+
50
+ return;
51
+ }
52
+
53
+ if (!is_string($encoding)) {
54
+ throw new InvalidArgumentException(sprintf('%s expects parameter 3 to be string, %s given', __METHOD__, gettype($encoding)));
55
+ }
56
+
57
+ $this->encoding = $encoding;
58
+
59
+ $this->document = new DOMDocument('1.0', $encoding);
60
+
61
+ $this->preserveWhiteSpace(false);
62
+
63
+ if ($string !== null) {
64
+ $this->load($string, $isFile, $type);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Create new document.
70
+ *
71
+ * @param string|null $string HTML or XML string or file path
72
+ * @param bool $isFile Indicates that in first parameter was passed to the file path
73
+ * @param string $encoding The document encoding
74
+ * @param string $type The document type
75
+ *
76
+ * @return \DiDom\Document
77
+ */
78
+ public static function create($string = null, $isFile = false, $encoding = 'UTF-8', $type = Document::TYPE_HTML)
79
+ {
80
+ return new Document($string, $isFile, $encoding, $type);
81
+ }
82
+
83
+ /**
84
+ * Create new element node.
85
+ *
86
+ * @param string $name The tag name of the element
87
+ * @param string|null $value The value of the element
88
+ * @param array $attributes The attributes of the element
89
+ *
90
+ * @return \DiDom\Element created element
91
+ */
92
+ public function createElement($name, $value = null, array $attributes = [])
93
+ {
94
+ $node = $this->document->createElement($name);
95
+
96
+ $element = new Element($node, $value, $attributes);
97
+
98
+ return $element;
99
+ }
100
+
101
+ /**
102
+ * Create new element node by CSS selector.
103
+ *
104
+ * @param string $selector
105
+ * @param string|null $value
106
+ * @param array $attributes
107
+ *
108
+ * @return \DiDom\Element
109
+ */
110
+ public function createElementBySelector($selector, $value = null, array $attributes = [])
111
+ {
112
+ $segments = Query::getSegments($selector);
113
+
114
+ $name = array_key_exists('tag', $segments) ? $segments['tag'] : 'div';
115
+
116
+ if (array_key_exists('attributes', $segments)) {
117
+ $attributes = array_merge($attributes, $segments['attributes']);
118
+ }
119
+
120
+ if (array_key_exists('id', $segments)) {
121
+ $attributes['id'] = $segments['id'];
122
+ }
123
+
124
+ if (array_key_exists('classes', $segments)) {
125
+ $attributes['class'] = implode(' ', $segments['classes']);
126
+ }
127
+
128
+ return $this->createElement($name, $value, $attributes);
129
+ }
130
+
131
+ /**
132
+ * Add new child at the end of the children.
133
+ *
134
+ * @param \DiDom\Element|\DOMNode|array $nodes The appended child
135
+ *
136
+ * @return \DiDom\Element|\DiDom\Element[]
137
+ *
138
+ * @throws \InvalidArgumentException if the passed argument is not an instance of \DOMNode or \DiDom\Element
139
+ */
140
+ public function appendChild($nodes)
141
+ {
142
+ $returnArray = true;
143
+
144
+ if (!is_array($nodes)) {
145
+ $nodes = [$nodes];
146
+
147
+ $returnArray = false;
148
+ }
149
+
150
+ $result = [];
151
+
152
+ foreach ($nodes as $node) {
153
+ if ($node instanceof Element) {
154
+ $node = $node->getNode();
155
+ }
156
+
157
+ if (!$node instanceof \DOMNode) {
158
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s\Element or DOMNode, %s given', __METHOD__, __NAMESPACE__, (is_object($node) ? get_class($node) : gettype($node))));
159
+ }
160
+
161
+ Errors::disable();
162
+
163
+ $cloned = $node->cloneNode(true);
164
+ $newNode = $this->document->importNode($cloned, true);
165
+
166
+ $result[] = $this->document->appendChild($newNode);
167
+
168
+ Errors::restore();
169
+ }
170
+
171
+ $result = array_map(function (\DOMNode $node) {
172
+ return new Element($node);
173
+ }, $result);
174
+
175
+ return $returnArray ? $result : $result[0];
176
+ }
177
+
178
+ /**
179
+ * Set preserveWhiteSpace property.
180
+ *
181
+ * @param bool $value
182
+ *
183
+ * @return \DiDom\Document
184
+ */
185
+ public function preserveWhiteSpace($value = true)
186
+ {
187
+ if (!is_bool($value)) {
188
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be boolean, %s given', __METHOD__, gettype($value)));
189
+ }
190
+
191
+ $this->document->preserveWhiteSpace = $value;
192
+
193
+ return $this;
194
+ }
195
+
196
+ /**
197
+ * Load HTML or XML.
198
+ *
199
+ * @param string $string HTML or XML string or file path
200
+ * @param bool $isFile Indicates that in first parameter was passed to the file path
201
+ * @param string $type Type of the document
202
+ * @param int|null $options Additional parameters
203
+ *
204
+ * @return \DiDom\Document
205
+ *
206
+ * @throws \InvalidArgumentException if first parameter is not a string
207
+ * @throws \InvalidArgumentException if document type parameter is not a string
208
+ * @throws \RuntimeException if document type is not HTML or XML
209
+ */
210
+ public function load($string, $isFile = false, $type = Document::TYPE_HTML, $options = null)
211
+ {
212
+ if (!is_string($string)) {
213
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($string) ? get_class($string) : gettype($string))));
214
+ }
215
+
216
+ if (!is_string($type)) {
217
+ throw new InvalidArgumentException(sprintf('%s expects parameter 3 to be string, %s given', __METHOD__, (is_object($type) ? get_class($type) : gettype($type))));
218
+ }
219
+
220
+ if (!in_array(strtolower($type), [Document::TYPE_HTML, Document::TYPE_XML])) {
221
+ throw new RuntimeException(sprintf('Document type must be "xml" or "html", %s given', $type));
222
+ }
223
+
224
+ if ($options === null) {
225
+ // LIBXML_HTML_NODEFDTD - prevents a default doctype being added when one is not found
226
+ $options = LIBXML_HTML_NODEFDTD;
227
+ }
228
+
229
+ if (!is_integer($options)) {
230
+ throw new InvalidArgumentException(sprintf('%s expects parameter 4 to be integer, %s given', __METHOD__, (is_object($options) ? get_class($options) : gettype($options))));
231
+ }
232
+
233
+ $string = trim($string);
234
+
235
+ if ($isFile) {
236
+ $string = $this->loadFile($string);
237
+ }
238
+
239
+ if (strtolower($type) === Document::TYPE_HTML) {
240
+ $string = Encoder::convertToHtmlEntities($string, $this->encoding);
241
+ }
242
+
243
+ $this->type = strtolower($type);
244
+
245
+ Errors::disable();
246
+
247
+ if ($this->type === Document::TYPE_HTML) {
248
+ $this->document->loadHtml($string, $options);
249
+ } else {
250
+ $this->document->loadXml($string, $options);
251
+ }
252
+
253
+ Errors::restore();
254
+
255
+ return $this;
256
+ }
257
+
258
+ /**
259
+ * Load HTML from a string.
260
+ *
261
+ * @param string $html The HTML string
262
+ * @param int|null $options Additional parameters
263
+ *
264
+ * @return \DiDom\Document
265
+ *
266
+ * @throws \InvalidArgumentException if the provided argument is not a string
267
+ */
268
+ public function loadHtml($html, $options = null)
269
+ {
270
+ return $this->load($html, false, Document::TYPE_HTML, $options);
271
+ }
272
+
273
+ /**
274
+ * Load HTML from a file.
275
+ *
276
+ * @param string $filename The path to the HTML file
277
+ * @param int|null $options Additional parameters
278
+ *
279
+ * @return \DiDom\Document
280
+ *
281
+ * @throws \InvalidArgumentException if the file path is not a string
282
+ * @throws \RuntimeException if the file does not exist
283
+ * @throws \RuntimeException if you are unable to load the file
284
+ */
285
+ public function loadHtmlFile($filename, $options = null)
286
+ {
287
+ return $this->load($filename, true, Document::TYPE_HTML, $options);
288
+ }
289
+
290
+ /**
291
+ * Load XML from a string.
292
+ *
293
+ * @param string $xml The XML string
294
+ * @param int|null $options Additional parameters
295
+ *
296
+ * @return \DiDom\Document
297
+ *
298
+ * @throws \InvalidArgumentException if the provided argument is not a string
299
+ */
300
+ public function loadXml($xml, $options = null)
301
+ {
302
+ return $this->load($xml, false, Document::TYPE_XML, $options);
303
+ }
304
+
305
+ /**
306
+ * Load XML from a file.
307
+ *
308
+ * @param string $filename The path to the XML file
309
+ * @param int|null $options Additional parameters
310
+ *
311
+ * @return \DiDom\Document
312
+ *
313
+ * @throws \InvalidArgumentException if the file path is not a string
314
+ * @throws \RuntimeException if the file does not exist
315
+ * @throws \RuntimeException if you are unable to load the file
316
+ */
317
+ public function loadXmlFile($filename, $options = null)
318
+ {
319
+ return $this->load($filename, true, Document::TYPE_XML, $options);
320
+ }
321
+
322
+ /**
323
+ * Reads entire file into a string.
324
+ *
325
+ * @param string $filename The path to the file
326
+ *
327
+ * @return string
328
+ *
329
+ * @throws \InvalidArgumentException if the file path is not a string
330
+ * @throws \RuntimeException if an error occurred
331
+ */
332
+ protected function loadFile($filename)
333
+ {
334
+ if (!is_string($filename)) {
335
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, gettype($filename)));
336
+ }
337
+
338
+ try {
339
+ $content = file_get_contents($filename);
340
+ } catch (\Exception $exception) {
341
+ throw new RuntimeException(sprintf('Could not load file %s', $filename));
342
+ }
343
+
344
+ if ($content === false) {
345
+ throw new RuntimeException(sprintf('Could not load file %s', $filename));
346
+ }
347
+
348
+ return $content;
349
+ }
350
+
351
+ /**
352
+ * Checks the existence of the node.
353
+ *
354
+ * @param string $expression XPath expression or CSS selector
355
+ * @param string $type The type of the expression
356
+ *
357
+ * @return bool
358
+ */
359
+ public function has($expression, $type = Query::TYPE_CSS)
360
+ {
361
+ $xpath = new DOMXPath($this->document);
362
+
363
+ $xpath->registerNamespace('php', 'http://php.net/xpath');
364
+ $xpath->registerPhpFunctions();
365
+
366
+ $expression = Query::compile($expression, $type);
367
+ $expression = sprintf('count(%s) > 0', $expression);
368
+
369
+ return $xpath->evaluate($expression);
370
+ }
371
+
372
+ /**
373
+ * Searches for an node in the DOM tree for a given XPath expression or a CSS selector.
374
+ *
375
+ * @param string $expression XPath expression or a CSS selector
376
+ * @param string $type The type of the expression
377
+ * @param bool $wrapNode Returns array of \DiDom\Element if true, otherwise array of \DOMElement
378
+ * @param \DOMElement|null $contextNode The node in which the search will be performed
379
+ *
380
+ * @return \DiDom\Element[]|\DOMElement[]
381
+ *
382
+ * @throws InvalidArgumentException if context node is not \DOMElement
383
+ */
384
+ public function find($expression, $type = Query::TYPE_CSS, $wrapNode = true, $contextNode = null)
385
+ {
386
+ $expression = Query::compile($expression, $type);
387
+
388
+ $xpath = new DOMXPath($this->document);
389
+
390
+ $xpath->registerNamespace('php', 'http://php.net/xpath');
391
+ $xpath->registerPhpFunctions();
392
+
393
+ if ($contextNode !== null) {
394
+ if ($contextNode instanceof Element) {
395
+ $contextNode = $contextNode->getNode();
396
+ }
397
+
398
+ if (!$contextNode instanceof \DOMElement) {
399
+ throw new InvalidArgumentException(sprintf('Argument 4 passed to %s must be an instance of %s\Element or DOMElement, %s given', __METHOD__, __NAMESPACE__, (is_object($contextNode) ? get_class($contextNode) : gettype($contextNode))));
400
+ }
401
+
402
+ if ($type === Query::TYPE_CSS) {
403
+ $expression = '.'.$expression;
404
+ }
405
+ }
406
+
407
+ $nodeList = $xpath->query($expression, $contextNode);
408
+
409
+ $result = [];
410
+
411
+ if ($wrapNode) {
412
+ foreach ($nodeList as $node) {
413
+ $result[] = $this->wrapNode($node);
414
+ }
415
+ } else {
416
+ foreach ($nodeList as $node) {
417
+ $result[] = $node;
418
+ }
419
+ }
420
+
421
+ return $result;
422
+ }
423
+
424
+ /**
425
+ * Searches for an node in the DOM tree and returns first element or null.
426
+ *
427
+ * @param string $expression XPath expression or a CSS selector
428
+ * @param string $type The type of the expression
429
+ * @param bool $wrapNode Returns array of \DiDom\Element if true, otherwise array of \DOMElement
430
+ * @param \DOMElement|null $contextNode The node in which the search will be performed
431
+ *
432
+ * @return \DiDom\Element|\DOMElement|null
433
+ */
434
+ public function first($expression, $type = Query::TYPE_CSS, $wrapNode = true, $contextNode = null)
435
+ {
436
+ $expression = Query::compile($expression, $type);
437
+
438
+ if ($contextNode !== null and $type === Query::TYPE_CSS) {
439
+ $expression = '.'.$expression;
440
+ }
441
+
442
+ $expression = sprintf('(%s)[1]', $expression);
443
+
444
+ $nodes = $this->find($expression, Query::TYPE_XPATH, false, $contextNode);
445
+
446
+ if (count($nodes) === 0) {
447
+ return null;
448
+ }
449
+
450
+ return $wrapNode ? $this->wrapNode($nodes[0]) : $nodes[0];
451
+ }
452
+
453
+ /**
454
+ * @param \DOMElement|\DOMText|\DOMAttr $node
455
+ *
456
+ * @return \DiDom\Element|string
457
+ *
458
+ * @throws InvalidArgumentException if node is not DOMElement, DOMText or DOMAttr
459
+ */
460
+ protected function wrapNode($node)
461
+ {
462
+ switch (get_class($node)) {
463
+ case 'DOMElement':
464
+ return new Element($node);
465
+
466
+ case 'DOMText':
467
+ return $node->data;
468
+
469
+ case 'DOMAttr':
470
+ return $node->value;
471
+ }
472
+
473
+ throw new InvalidArgumentException(sprintf('Unknown node type "%s"', get_class($node)));
474
+ }
475
+
476
+ /**
477
+ * Searches for an node in the DOM tree for a given XPath expression.
478
+ *
479
+ * @param string $expression XPath expression
480
+ * @param bool $wrapNode Returns array of \DiDom\Element if true, otherwise array of \DOMElement
481
+ * @param \DOMElement $contextNode The node in which the search will be performed
482
+ *
483
+ * @return \DiDom\Element[]|\DOMElement[]
484
+ */
485
+ public function xpath($expression, $wrapNode = true, $contextNode = null)
486
+ {
487
+ return $this->find($expression, Query::TYPE_XPATH, $wrapNode, $contextNode);
488
+ }
489
+
490
+ /**
491
+ * Counts nodes for a given XPath expression or a CSS selector.
492
+ *
493
+ * @param string $expression XPath expression or CSS selector
494
+ * @param string $type The type of the expression
495
+ *
496
+ * @return int
497
+ */
498
+ public function count($expression, $type = Query::TYPE_CSS)
499
+ {
500
+ $xpath = new DOMXPath($this->document);
501
+
502
+ $xpath->registerNamespace('php', 'http://php.net/xpath');
503
+ $xpath->registerPhpFunctions();
504
+
505
+ $expression = Query::compile($expression, $type);
506
+ $expression = sprintf('count(%s)', $expression);
507
+
508
+ return (int) $xpath->evaluate($expression);
509
+ }
510
+
511
+ /**
512
+ * Dumps the internal document into a string using HTML formatting.
513
+ *
514
+ * @return string The document html
515
+ */
516
+ public function html()
517
+ {
518
+ return trim($this->document->saveHTML($this->document));
519
+ }
520
+
521
+ /**
522
+ * Dumps the internal document into a string using XML formatting.
523
+ *
524
+ * @param int $options Additional options
525
+ *
526
+ * @return string The document xml
527
+ */
528
+ public function xml($options = 0)
529
+ {
530
+ return trim($this->document->saveXML($this->document, $options));
531
+ }
532
+
533
+ /**
534
+ * Nicely formats output with indentation and extra space.
535
+ *
536
+ * @param bool $format Formats output if true
537
+ *
538
+ * @return \DiDom\Document
539
+ */
540
+ public function format($format = true)
541
+ {
542
+ if (!is_bool($format)) {
543
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be boolean, %s given', __METHOD__, gettype($format)));
544
+ }
545
+
546
+ $this->document->formatOutput = $format;
547
+
548
+ return $this;
549
+ }
550
+
551
+ /**
552
+ * Get the text content of this node and its descendants.
553
+ *
554
+ * @return string
555
+ */
556
+ public function text()
557
+ {
558
+ return $this->getElement()->textContent;
559
+ }
560
+
561
+ /**
562
+ * Indicates if two documents are the same document.
563
+ *
564
+ * @param \DiDom\Document|\DOMDocument $document The compared document
565
+ *
566
+ * @return bool
567
+ *
568
+ * @throws \InvalidArgumentException if the provided argument is not an instance of \DOMDocument or \DiDom\Document
569
+ */
570
+ public function is($document)
571
+ {
572
+ if ($document instanceof Document) {
573
+ $element = $document->getElement();
574
+ } else {
575
+ if (!$document instanceof DOMDocument) {
576
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s or DOMDocument, %s given', __METHOD__, __CLASS__, (is_object($document) ? get_class($document) : gettype($document))));
577
+ }
578
+
579
+ $element = $document->documentElement;
580
+ }
581
+
582
+ if ($element === null) {
583
+ return false;
584
+ }
585
+
586
+ return $this->getElement()->isSameNode($element);
587
+ }
588
+
589
+ /**
590
+ * Returns the type of document (XML or HTML).
591
+ *
592
+ * @return string
593
+ */
594
+ public function getType()
595
+ {
596
+ return $this->type;
597
+ }
598
+
599
+ /**
600
+ * Returns the encoding of document (XML or HTML).
601
+ *
602
+ * @return string
603
+ */
604
+ public function getEncoding()
605
+ {
606
+ return $this->encoding;
607
+ }
608
+
609
+ /**
610
+ * @return \DOMDocument
611
+ */
612
+ public function getDocument()
613
+ {
614
+ return $this->document;
615
+ }
616
+
617
+ /**
618
+ * @return \DOMElement
619
+ */
620
+ public function getElement()
621
+ {
622
+ return $this->document->documentElement;
623
+ }
624
+
625
+ /**
626
+ * @return \DiDom\Element
627
+ */
628
+ public function toElement()
629
+ {
630
+ if ($this->document->documentElement === null) {
631
+ throw new RuntimeException('Cannot convert empty document to Element');
632
+ }
633
+
634
+ return new Element($this->document->documentElement);
635
+ }
636
+
637
+ /**
638
+ * Convert the document to its string representation.
639
+ *
640
+ * @return string
641
+ */
642
+ public function __toString()
643
+ {
644
+ return $this->type === Document::TYPE_HTML ? $this->html() : $this->xml();
645
+ }
646
+
647
+ /**
648
+ * Searches for an node in the DOM tree for a given XPath expression or a CSS selector.
649
+ *
650
+ * @param string $expression XPath expression or a CSS selector
651
+ * @param string $type The type of the expression
652
+ * @param bool $wrapNode Returns array of \DiDom\Element if true, otherwise array of \DOMElement
653
+ * @param \DOMElement|null $contextNode The node in which the search will be performed
654
+ *
655
+ * @return \DiDom\Element[]|\DOMElement[]
656
+ *
657
+ * @deprecated Not longer recommended, use Document::find() instead.
658
+ */
659
+ public function __invoke($expression, $type = Query::TYPE_CSS, $wrapNode = true, $contextNode = null)
660
+ {
661
+ return $this->find($expression, $type, $wrapNode, $contextNode);
662
+ }
663
+ }
vendor/imangazaliev/didom/src/DiDom/Element.php ADDED
@@ -0,0 +1,1445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ use DOMDocument;
6
+ use DOMElement;
7
+ use DOMNode;
8
+ use InvalidArgumentException;
9
+ use LogicException;
10
+ use RuntimeException;
11
+
12
+ /**
13
+ * @property string $tag
14
+ */
15
+ class Element
16
+ {
17
+ /**
18
+ * The DOM element instance.
19
+ *
20
+ * @var \DOMElement|\DOMText|\DOMComment
21
+ */
22
+ protected $node;
23
+
24
+ /**
25
+ * @var \DiDom\ClassAttribute
26
+ */
27
+ protected $classAttribute;
28
+
29
+ /**
30
+ * @var \DiDom\StyleAttribute
31
+ */
32
+ protected $styleAttribute;
33
+
34
+ /**
35
+ * Constructor.
36
+ *
37
+ * @param \DOMElement|\DOMText|\DOMComment|string $name The tag name of the element
38
+ * @param string|null $value The value of the element
39
+ * @param array $attributes The attributes of the element
40
+ */
41
+ public function __construct($name, $value = null, array $attributes = [])
42
+ {
43
+ if (is_string($name)) {
44
+ $document = new DOMDocument('1.0', 'UTF-8');
45
+
46
+ $node = $document->createElement($name);
47
+
48
+ $this->setNode($node);
49
+ } else {
50
+ $this->setNode($name);
51
+ }
52
+
53
+ if ($value !== null) {
54
+ $this->setValue($value);
55
+ }
56
+
57
+ foreach ($attributes as $name => $value) {
58
+ $this->setAttribute($name, $value);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Create new element.
64
+ *
65
+ * @param \DOMNode|string $name The tag name of the element
66
+ * @param string|null $value The value of the element
67
+ * @param array $attributes The attributes of the element
68
+ *
69
+ * @return \DiDom\Element
70
+ */
71
+ public static function create($name, $value = null, array $attributes = [])
72
+ {
73
+ return new Element($name, $value, $attributes);
74
+ }
75
+
76
+ /**
77
+ * Create new element node by CSS selector.
78
+ *
79
+ * @param string $selector
80
+ * @param string|null $value
81
+ * @param array $attributes
82
+ *
83
+ * @return \DiDom\Element
84
+ */
85
+ public static function createBySelector($selector, $value = null, array $attributes = [])
86
+ {
87
+ return Document::create()->createElementBySelector($selector, $value, $attributes);
88
+ }
89
+
90
+ /**
91
+ * Adds new child at the start of the children.
92
+ *
93
+ * @param \DiDom\Element|\DOMNode|array $nodes The prepended child
94
+ *
95
+ * @return \DiDom\Element|\DiDom\Element[]
96
+ *
97
+ * @throws \LogicException if current node has no owner document
98
+ * @throws \InvalidArgumentException if the provided argument is not an instance of \DOMNode or \DiDom\Element
99
+ */
100
+ public function prependChild($nodes)
101
+ {
102
+ if ($this->node->ownerDocument === null) {
103
+ throw new LogicException('Can not prepend child to element without owner document');
104
+ }
105
+
106
+ $returnArray = true;
107
+
108
+ if (!is_array($nodes)) {
109
+ $nodes = [$nodes];
110
+
111
+ $returnArray = false;
112
+ }
113
+
114
+ $nodes = array_reverse($nodes);
115
+
116
+ $result = [];
117
+
118
+ $referenceNode = $this->node->firstChild;
119
+
120
+ foreach ($nodes as $node) {
121
+ $result[] = $this->insertBefore($node, $referenceNode);
122
+
123
+ $referenceNode = $this->node->firstChild;
124
+ }
125
+
126
+ return $returnArray ? $result : $result[0];
127
+ }
128
+
129
+ /**
130
+ * Adds new child at the end of the children.
131
+ *
132
+ * @param \DiDom\Element|\DOMNode|array $nodes The appended child
133
+ *
134
+ * @return \DiDom\Element|\DiDom\Element[]
135
+ *
136
+ * @throws \LogicException if current node has no owner document
137
+ * @throws \InvalidArgumentException if the provided argument is not an instance of \DOMNode or \DiDom\Element
138
+ */
139
+ public function appendChild($nodes)
140
+ {
141
+ if ($this->node->ownerDocument === null) {
142
+ throw new LogicException('Can not append child to element without owner document');
143
+ }
144
+
145
+ $returnArray = true;
146
+
147
+ if (!is_array($nodes)) {
148
+ $nodes = [$nodes];
149
+
150
+ $returnArray = false;
151
+ }
152
+
153
+ $result = [];
154
+
155
+ Errors::disable();
156
+
157
+ foreach ($nodes as $node) {
158
+ if ($node instanceof Element) {
159
+ $node = $node->getNode();
160
+ }
161
+
162
+ if (!$node instanceof \DOMNode) {
163
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s\Element or DOMNode, %s given', __METHOD__, __NAMESPACE__, (is_object($node) ? get_class($node) : gettype($node))));
164
+ }
165
+
166
+ $clonedNode = $node->cloneNode(true);
167
+ $newNode = $this->node->ownerDocument->importNode($clonedNode, true);
168
+
169
+ $result[] = $this->node->appendChild($newNode);
170
+ }
171
+
172
+ Errors::restore();
173
+
174
+ $result = array_map(function (\DOMNode $node) {
175
+ return new Element($node);
176
+ }, $result);
177
+
178
+ return $returnArray ? $result : $result[0];
179
+ }
180
+
181
+ /**
182
+ * Adds a new child before a reference node.
183
+ *
184
+ * @param \DiDom\Element|\DOMNode $node The new node
185
+ * @param \DiDom\Element|\DOMNode|null $referenceNode The reference node
186
+ *
187
+ * @return \DiDom\Element
188
+ *
189
+ * @throws \LogicException if current node has no owner document
190
+ * @throws \InvalidArgumentException if $node is not an instance of \DOMNode or \DiDom\Element
191
+ * @throws \InvalidArgumentException if $referenceNode is not an instance of \DOMNode or \DiDom\Element
192
+ */
193
+ public function insertBefore($node, $referenceNode = null)
194
+ {
195
+ if ($this->node->ownerDocument === null) {
196
+ throw new LogicException('Can not insert child to element without owner document');
197
+ }
198
+
199
+ if ($node instanceof Element) {
200
+ $node = $node->getNode();
201
+ }
202
+
203
+ if (!$node instanceof \DOMNode) {
204
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s\Element or DOMNode, %s given', __METHOD__, __NAMESPACE__, (is_object($node) ? get_class($node) : gettype($node))));
205
+ }
206
+
207
+ if ($referenceNode !== null) {
208
+ if ($referenceNode instanceof Element) {
209
+ $referenceNode = $referenceNode->getNode();
210
+ }
211
+
212
+ if (!$referenceNode instanceof \DOMNode) {
213
+ throw new InvalidArgumentException(sprintf('Argument 2 passed to %s must be an instance of %s\Element or DOMNode, %s given', __METHOD__, __NAMESPACE__, (is_object($referenceNode) ? get_class($referenceNode) : gettype($referenceNode))));
214
+ }
215
+ }
216
+
217
+ Errors::disable();
218
+
219
+ $clonedNode = $node->cloneNode(true);
220
+ $newNode = $this->node->ownerDocument->importNode($clonedNode, true);
221
+
222
+ $insertedNode = $this->node->insertBefore($newNode, $referenceNode);
223
+
224
+ Errors::restore();
225
+
226
+ return new Element($insertedNode);
227
+ }
228
+
229
+ /**
230
+ * Adds a new child after a reference node.
231
+ *
232
+ * @param \DiDom\Element|\DOMNode $node The new node
233
+ * @param \DiDom\Element|\DOMNode|null $referenceNode The reference node
234
+ *
235
+ * @return \DiDom\Element
236
+ *
237
+ * @throws \LogicException if current node has no owner document
238
+ * @throws \InvalidArgumentException if $node is not an instance of \DOMNode or \DiDom\Element
239
+ * @throws \InvalidArgumentException if $referenceNode is not an instance of \DOMNode or \DiDom\Element
240
+ */
241
+ public function insertAfter($node, $referenceNode = null)
242
+ {
243
+ if ($referenceNode === null) {
244
+ return $this->insertBefore($node);
245
+ }
246
+
247
+ if ($referenceNode instanceof Element) {
248
+ $referenceNode = $referenceNode->getNode();
249
+ }
250
+
251
+ if (!$referenceNode instanceof \DOMNode) {
252
+ throw new InvalidArgumentException(sprintf('Argument 2 passed to %s must be an instance of %s\Element or DOMNode, %s given', __METHOD__, __NAMESPACE__, (is_object($referenceNode) ? get_class($referenceNode) : gettype($referenceNode))));
253
+ }
254
+
255
+ return $this->insertBefore($node, $referenceNode->nextSibling);
256
+ }
257
+
258
+ /**
259
+ * Checks the existence of the node.
260
+ *
261
+ * @param string $expression XPath expression or CSS selector
262
+ * @param string $type The type of the expression
263
+ *
264
+ * @return bool
265
+ */
266
+ public function has($expression, $type = Query::TYPE_CSS)
267
+ {
268
+ return $this->toDocument()->has($expression, $type);
269
+ }
270
+
271
+ /**
272
+ * Searches for an node in the DOM tree for a given XPath expression or a CSS selector.
273
+ *
274
+ * @param string $expression XPath expression or a CSS selector
275
+ * @param string $type The type of the expression
276
+ * @param bool $wrapElement Returns array of \DiDom\Element if true, otherwise array of \DOMElement
277
+ *
278
+ * @return \DiDom\Element[]|\DOMElement[]
279
+ */
280
+ public function find($expression, $type = Query::TYPE_CSS, $wrapElement = true)
281
+ {
282
+ return $this->toDocument()->find($expression, $type, $wrapElement);
283
+ }
284
+
285
+ /**
286
+ * Searches for an node in the owner document using current node as context.
287
+ *
288
+ * @param string $expression XPath expression or a CSS selector
289
+ * @param string $type The type of the expression
290
+ * @param bool $wrapNode Returns array of \DiDom\Element if true, otherwise array of \DOMElement
291
+ *
292
+ * @return \DiDom\Element[]|\DOMElement[]
293
+ *
294
+ * @throws \LogicException if current node has no owner document
295
+ */
296
+ public function findInDocument($expression, $type = Query::TYPE_CSS, $wrapNode = true)
297
+ {
298
+ $ownerDocument = $this->getDocument();
299
+
300
+ if ($ownerDocument === null) {
301
+ throw new LogicException('Can not search in context without owner document');
302
+ }
303
+
304
+ return $ownerDocument->find($expression, $type, $wrapNode, $this->node);
305
+ }
306
+
307
+ /**
308
+ * Searches for an node in the DOM tree and returns first element or null.
309
+ *
310
+ * @param string $expression XPath expression or a CSS selector
311
+ * @param string $type The type of the expression
312
+ * @param bool $wrapNode Returns \DiDom\Element if true, otherwise \DOMElement
313
+ *
314
+ * @return \DiDom\Element|\DOMElement|null
315
+ */
316
+ public function first($expression, $type = Query::TYPE_CSS, $wrapNode = true)
317
+ {
318
+ return $this->toDocument()->first($expression, $type, $wrapNode);
319
+ }
320
+
321
+ /**
322
+ * Searches for an node in the owner document using current node as context and returns first element or null.
323
+ *
324
+ * @param string $expression XPath expression or a CSS selector
325
+ * @param string $type The type of the expression
326
+ * @param bool $wrapNode Returns \DiDom\Element if true, otherwise \DOMElement
327
+ *
328
+ * @return \DiDom\Element|\DOMElement|null
329
+ */
330
+ public function firstInDocument($expression, $type = Query::TYPE_CSS, $wrapNode = true)
331
+ {
332
+ $ownerDocument = $this->getDocument();
333
+
334
+ if ($ownerDocument === null) {
335
+ throw new LogicException('Can not search in context without owner document');
336
+ }
337
+
338
+ return $ownerDocument->first($expression, $type, $wrapNode, $this->node);
339
+ }
340
+
341
+ /**
342
+ * Searches for an node in the DOM tree for a given XPath expression.
343
+ *
344
+ * @param string $expression XPath expression
345
+ * @param bool $wrapNode Returns array of \DiDom\Element if true, otherwise array of \DOMElement
346
+ *
347
+ * @return \DiDom\Element[]|\DOMElement[]
348
+ */
349
+ public function xpath($expression, $wrapNode = true)
350
+ {
351
+ return $this->find($expression, Query::TYPE_XPATH, $wrapNode);
352
+ }
353
+
354
+ /**
355
+ * Counts nodes for a given XPath expression or a CSS selector.
356
+ *
357
+ * @param string $expression XPath expression or CSS selector
358
+ * @param string $type The type of the expression
359
+ *
360
+ * @return int
361
+ */
362
+ public function count($expression, $type = Query::TYPE_CSS)
363
+ {
364
+ return $this->toDocument()->count($expression, $type);
365
+ }
366
+
367
+ /**
368
+ * Checks that the node matches selector.
369
+ *
370
+ * @param string $selector CSS selector
371
+ * @param bool $strict
372
+ *
373
+ * @return bool
374
+ *
375
+ * @throws \RuntimeException if tag name is not specified in strict mode
376
+ */
377
+ public function matches($selector, $strict = false)
378
+ {
379
+ if (!is_string($selector)) {
380
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, gettype($selector)));
381
+ }
382
+
383
+ if (!$this->node instanceof \DOMElement) {
384
+ return false;
385
+ }
386
+
387
+ if ($selector === '*') {
388
+ return true;
389
+ }
390
+
391
+ if (!$strict) {
392
+ $innerHtml = $this->html();
393
+ $html = "<root>$innerHtml</root>";
394
+
395
+ $selector = 'root > '.trim($selector);
396
+
397
+ $document = new Document();
398
+
399
+ $document->loadHtml($html, LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED);
400
+
401
+ return $document->has($selector);
402
+ }
403
+
404
+ $segments = Query::getSegments($selector);
405
+
406
+ if (!array_key_exists('tag', $segments)) {
407
+ throw new RuntimeException(sprintf('Tag name must be specified in %s', $selector));
408
+ }
409
+
410
+ if ($segments['tag'] !== $this->tag and $segments['tag'] !== '*') {
411
+ return false;
412
+ }
413
+
414
+ $segments['id'] = array_key_exists('id', $segments) ? $segments['id'] : null;
415
+
416
+ if ($segments['id'] !== $this->getAttribute('id')) {
417
+ return false;
418
+ }
419
+
420
+ $classes = $this->hasAttribute('class') ? explode(' ', trim($this->getAttribute('class'))) : [];
421
+
422
+ $segments['classes'] = array_key_exists('classes', $segments) ? $segments['classes'] : [];
423
+
424
+ $diff1 = array_diff($segments['classes'], $classes);
425
+ $diff2 = array_diff($classes, $segments['classes']);
426
+
427
+ if (count($diff1) > 0 or count($diff2) > 0) {
428
+ return false;
429
+ }
430
+
431
+ $attributes = $this->attributes();
432
+
433
+ unset($attributes['id']);
434
+ unset($attributes['class']);
435
+
436
+ $segments['attributes'] = array_key_exists('attributes', $segments) ? $segments['attributes'] : [];
437
+
438
+ $diff1 = array_diff_assoc($segments['attributes'], $attributes);
439
+ $diff2 = array_diff_assoc($attributes, $segments['attributes']);
440
+
441
+ if (count($diff1) > 0 or count($diff2) > 0) {
442
+ return false;
443
+ }
444
+
445
+ return true;
446
+ }
447
+
448
+ /**
449
+ * Determine if an attribute exists on the element.
450
+ *
451
+ * @param string $name The attribute name
452
+ *
453
+ * @return bool
454
+ */
455
+ public function hasAttribute($name)
456
+ {
457
+ return $this->node->hasAttribute($name);
458
+ }
459
+
460
+ /**
461
+ * Set an attribute on the element.
462
+ *
463
+ * @param string $name The attribute name
464
+ * @param string $value The attribute value
465
+ *
466
+ * @return \DiDom\Element
467
+ */
468
+ public function setAttribute($name, $value)
469
+ {
470
+ if (is_numeric($value)) {
471
+ $value = (string) $value;
472
+ }
473
+
474
+ if (!is_string($value) and $value !== null) {
475
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string or null, %s given', __METHOD__, (is_object($value) ? get_class($value) : gettype($value))));
476
+ }
477
+
478
+ $this->node->setAttribute($name, $value);
479
+
480
+ return $this;
481
+ }
482
+
483
+ /**
484
+ * Access to the element's attributes.
485
+ *
486
+ * @param string $name The attribute name
487
+ * @param string|null $default The value returned if the attribute does not exist
488
+ *
489
+ * @return string|null The value of the attribute or null if attribute does not exist
490
+ */
491
+ public function getAttribute($name, $default = null)
492
+ {
493
+ if ($this->hasAttribute($name)) {
494
+ return $this->node->getAttribute($name);
495
+ }
496
+
497
+ return $default;
498
+ }
499
+
500
+ /**
501
+ * Unset an attribute on the element.
502
+ *
503
+ * @param string $name The attribute name
504
+ *
505
+ * @return \DiDom\Element
506
+ */
507
+ public function removeAttribute($name)
508
+ {
509
+ $this->node->removeAttribute($name);
510
+
511
+ return $this;
512
+ }
513
+
514
+ /**
515
+ * Unset all attributes of the element.
516
+ *
517
+ * @param string[] $exclusions
518
+ *
519
+ * @return \DiDom\Element
520
+ */
521
+ public function removeAllAttributes(array $exclusions = [])
522
+ {
523
+ if (!$this->node instanceof DOMElement) {
524
+ return $this;
525
+ }
526
+
527
+ foreach ($this->attributes() as $name => $value) {
528
+ if (in_array($name, $exclusions)) {
529
+ continue;
530
+ }
531
+
532
+ $this->node->removeAttribute($name);
533
+ }
534
+
535
+ return $this;
536
+ }
537
+
538
+ /**
539
+ * Alias for getAttribute and setAttribute methods.
540
+ *
541
+ * @param string $name The attribute name
542
+ * @param string|null $value The attribute value or null if the attribute does not exist
543
+ *
544
+ * @return string|null|\DiDom\Element
545
+ */
546
+ public function attr($name, $value = null)
547
+ {
548
+ if ($value === null) {
549
+ return $this->getAttribute($name);
550
+ }
551
+
552
+ return $this->setAttribute($name, $value);
553
+ }
554
+
555
+ /**
556
+ * Returns the node attributes or null, if it is not DOMElement.
557
+ *
558
+ * @param string[] $names
559
+ *
560
+ * @return array|null
561
+ */
562
+ public function attributes(array $names = null)
563
+ {
564
+ if (!$this->node instanceof DOMElement) {
565
+ return null;
566
+ }
567
+
568
+ if ($names === null) {
569
+ $result = [];
570
+
571
+ foreach ($this->node->attributes as $name => $attribute) {
572
+ $result[$name] = $attribute->value;
573
+ }
574
+
575
+ return $result;
576
+ }
577
+
578
+ $result = [];
579
+
580
+ foreach ($this->node->attributes as $name => $attribute) {
581
+ if (in_array($name, $names)) {
582
+ $result[$name] = $attribute->value;
583
+ }
584
+ }
585
+
586
+ return $result;
587
+ }
588
+
589
+ /**
590
+ * @return \DiDom\ClassAttribute
591
+ *
592
+ * @throws \LogicException if the node is not an instance of \DOMElement
593
+ */
594
+ public function classes()
595
+ {
596
+ if ($this->classAttribute !== null) {
597
+ return $this->classAttribute;
598
+ }
599
+
600
+ if (!$this->isElementNode()) {
601
+ throw new LogicException('Class attribute is available only for element nodes');
602
+ }
603
+
604
+ $this->classAttribute = new ClassAttribute($this);
605
+
606
+ return $this->classAttribute;
607
+ }
608
+
609
+ /**
610
+ * @return \DiDom\StyleAttribute
611
+ *
612
+ * @throws \LogicException if the node is not an instance of \DOMElement
613
+ */
614
+ public function style()
615
+ {
616
+ if ($this->styleAttribute !== null) {
617
+ return $this->styleAttribute;
618
+ }
619
+
620
+ if (!$this->isElementNode()) {
621
+ throw new LogicException('Style attribute is available only for element nodes');
622
+ }
623
+
624
+ $this->styleAttribute = new StyleAttribute($this);
625
+
626
+ return $this->styleAttribute;
627
+ }
628
+
629
+ /**
630
+ * Dumps the node into a string using HTML formatting (including child nodes).
631
+ *
632
+ * @return string
633
+ */
634
+ public function html()
635
+ {
636
+ return $this->toDocument()->html();
637
+ }
638
+
639
+ /**
640
+ * Dumps the node into a string using HTML formatting (without child nodes).
641
+ *
642
+ * @return string
643
+ */
644
+ public function outerHtml()
645
+ {
646
+ $document = new DOMDocument();
647
+
648
+ $importedNode = $document->importNode($this->node);
649
+
650
+ return $document->saveHTML($importedNode);
651
+ }
652
+
653
+ /**
654
+ * Dumps the node descendants into a string using HTML formatting.
655
+ *
656
+ * @param string $delimiter
657
+ *
658
+ * @return string
659
+ */
660
+ public function innerHtml($delimiter = '')
661
+ {
662
+ $innerHtml = [];
663
+
664
+ foreach ($this->node->childNodes as $childNode) {
665
+ $innerHtml[] = $childNode->ownerDocument->saveHTML($childNode);
666
+ }
667
+
668
+ return implode($delimiter, $innerHtml);
669
+ }
670
+
671
+ /**
672
+ * Sets inner HTML.
673
+ *
674
+ * @param string $html
675
+ *
676
+ * @return \DiDom\Element
677
+ *
678
+ * @throws InvalidArgumentException if passed argument is not a string
679
+ */
680
+ public function setInnerHtml($html)
681
+ {
682
+ if (!is_string($html)) {
683
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($html) ? get_class($html) : gettype($html))));
684
+ }
685
+
686
+ $this->removeChildren();
687
+
688
+ if ($html !== '') {
689
+ Errors::disable();
690
+
691
+ $html = "<htmlfragment>$html</htmlfragment>";
692
+
693
+ $document = new Document($html);
694
+
695
+ $fragment = $document->first('htmlfragment')->getNode();
696
+
697
+ foreach ($fragment->childNodes as $node) {
698
+ $newNode = $this->node->ownerDocument->importNode($node, true);
699
+
700
+ $this->node->appendChild($newNode);
701
+ }
702
+
703
+ Errors::restore();
704
+ }
705
+
706
+ return $this;
707
+ }
708
+
709
+ /**
710
+ * Dumps the node into a string using XML formatting.
711
+ *
712
+ * @param int $options Additional options
713
+ *
714
+ * @return string The node XML
715
+ */
716
+ public function xml($options = 0)
717
+ {
718
+ return $this->toDocument()->xml($options);
719
+ }
720
+
721
+ /**
722
+ * Get the text content of this node and its descendants.
723
+ *
724
+ * @return string The node value
725
+ */
726
+ public function text()
727
+ {
728
+ return $this->node->textContent;
729
+ }
730
+
731
+ /**
732
+ * Set the value of this node.
733
+ *
734
+ * @param string $value The new value of the node
735
+ *
736
+ * @return \DiDom\Element
737
+ *
738
+ * @throws \InvalidArgumentException if value is not string
739
+ */
740
+ public function setValue($value)
741
+ {
742
+ if (is_numeric($value)) {
743
+ $value = (string) $value;
744
+ }
745
+
746
+ if (!is_string($value) and $value !== null) {
747
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($value) ? get_class($value) : gettype($value))));
748
+ }
749
+
750
+ $this->node->nodeValue = $value;
751
+
752
+ return $this;
753
+ }
754
+
755
+ /**
756
+ * Returns true if current node is DOMElement.
757
+ *
758
+ * @return bool
759
+ */
760
+ public function isElementNode()
761
+ {
762
+ return $this->node instanceof \DOMElement;
763
+ }
764
+
765
+ /**
766
+ * Returns true if current node is DOMText.
767
+ *
768
+ * @return bool
769
+ */
770
+ public function isTextNode()
771
+ {
772
+ return $this->node instanceof \DOMText;
773
+ }
774
+
775
+ /**
776
+ * Returns true if current node is DOMComment.
777
+ *
778
+ * @return bool
779
+ */
780
+ public function isCommentNode()
781
+ {
782
+ return $this->node instanceof \DOMComment;
783
+ }
784
+
785
+ /**
786
+ * Indicates if two nodes are the same node.
787
+ *
788
+ * @param \DiDom\Element|\DOMNode $node
789
+ *
790
+ * @return bool
791
+ *
792
+ * @throws \InvalidArgumentException if the provided argument is not an instance of \DOMNode
793
+ */
794
+ public function is($node)
795
+ {
796
+ if ($node instanceof Element) {
797
+ $node = $node->getNode();
798
+ }
799
+
800
+ if (!$node instanceof DOMNode) {
801
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s or DOMNode, %s given', __METHOD__, __CLASS__, (is_object($node) ? get_class($node) : gettype($node))));
802
+ }
803
+
804
+ return $this->node->isSameNode($node);
805
+ }
806
+
807
+ /**
808
+ * @return \DiDom\Element|\DiDom\Document|null
809
+ */
810
+ public function parent()
811
+ {
812
+ if ($this->node->parentNode === null) {
813
+ return null;
814
+ }
815
+
816
+ if ($this->node->parentNode instanceof \DOMDocument) {
817
+ return new Document($this->node->parentNode);
818
+ }
819
+
820
+ return new Element($this->node->parentNode);
821
+ }
822
+
823
+ /**
824
+ * Returns first parent node matches passed selector.
825
+ *
826
+ * @param string $selector
827
+ * @param bool $strict
828
+ *
829
+ * @return \DiDom\Element|null
830
+ */
831
+ public function closest($selector, $strict = false)
832
+ {
833
+ $node = $this;
834
+
835
+ while (true) {
836
+ $parent = $node->parent();
837
+
838
+ if ($parent === null or $parent instanceof Document) {
839
+ return null;
840
+ }
841
+
842
+ if ($parent->matches($selector, $strict)) {
843
+ return $parent;
844
+ }
845
+
846
+ $node = $parent;
847
+ }
848
+ }
849
+
850
+ /**
851
+ * @param string|null $selector
852
+ * @param string|null $nodeType
853
+ *
854
+ * @return \DiDom\Element|null
855
+ *
856
+ * @throws \InvalidArgumentException if node type is not string
857
+ * @throws \RuntimeException if node type is invalid
858
+ * @throws \LogicException if selector used with non DOMElement node type
859
+ */
860
+ public function previousSibling($selector = null, $nodeType = null)
861
+ {
862
+ if ($this->node->previousSibling === null) {
863
+ return null;
864
+ }
865
+
866
+ if ($selector === null and $nodeType === null) {
867
+ return new Element($this->node->previousSibling);
868
+ }
869
+
870
+ if ($selector !== null and $nodeType === null) {
871
+ $nodeType = 'DOMElement';
872
+ }
873
+
874
+ if (!is_string($nodeType)) {
875
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string, %s given', __METHOD__, gettype($nodeType)));
876
+ }
877
+
878
+ $allowedTypes = ['DOMElement', 'DOMText', 'DOMComment'];
879
+
880
+ if (!in_array($nodeType, $allowedTypes)) {
881
+ throw new RuntimeException(sprintf('Unknown node type "%s". Allowed types: %s', $nodeType, implode(', ', $allowedTypes)));
882
+ }
883
+
884
+ if ($selector !== null and $nodeType !== 'DOMElement') {
885
+ throw new LogicException(sprintf('Selector can be used only with DOMElement node type, %s given', $nodeType));
886
+ }
887
+
888
+ $node = $this->node->previousSibling;
889
+
890
+ while ($node !== null) {
891
+ if (get_class($node) !== $nodeType) {
892
+ $node = $node->previousSibling;
893
+
894
+ continue;
895
+ }
896
+
897
+ $element = new Element($node);
898
+
899
+ if ($selector === null) {
900
+ return $element;
901
+ }
902
+
903
+ if ($element->matches($selector)) {
904
+ return $element;
905
+ }
906
+
907
+ $node = $node->previousSibling;
908
+ }
909
+
910
+ return new Element($this->node->previousSibling);
911
+ }
912
+
913
+ /**
914
+ * @param string|null $selector
915
+ * @param string|null $nodeType
916
+ *
917
+ * @return \DiDom\Element[]
918
+ *
919
+ * @throws \InvalidArgumentException if node type is not string
920
+ * @throws \RuntimeException if node type is invalid
921
+ * @throws \LogicException if selector used with non DOMElement node type
922
+ */
923
+ public function previousSiblings($selector = null, $nodeType = null)
924
+ {
925
+ if ($this->node->previousSibling === null) {
926
+ return [];
927
+ }
928
+
929
+ if ($selector !== null and $nodeType === null) {
930
+ $nodeType = 'DOMElement';
931
+ }
932
+
933
+ if ($nodeType !== null) {
934
+ if (!is_string($nodeType)) {
935
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string, %s given', __METHOD__, gettype($nodeType)));
936
+ }
937
+
938
+ $allowedTypes = ['DOMElement', 'DOMText', 'DOMComment'];
939
+
940
+ if (!in_array($nodeType, $allowedTypes)) {
941
+ throw new RuntimeException(sprintf('Unknown node type "%s". Allowed types: %s', $nodeType, implode(', ', $allowedTypes)));
942
+ }
943
+ }
944
+
945
+ if ($selector !== null and $nodeType !== 'DOMElement') {
946
+ throw new LogicException(sprintf('Selector can be used only with DOMElement node type, %s given', $nodeType));
947
+ }
948
+
949
+ $result = [];
950
+
951
+ $node = $this->node->previousSibling;
952
+
953
+ while ($node !== null) {
954
+ $element = new Element($node);
955
+
956
+ if ($nodeType === null) {
957
+ $result[] = $element;
958
+
959
+ $node = $node->previousSibling;
960
+
961
+ continue;
962
+ }
963
+
964
+ if (get_class($node) !== $nodeType) {
965
+ $node = $node->previousSibling;
966
+
967
+ continue;
968
+ }
969
+
970
+ if ($selector === null) {
971
+ $result[] = $element;
972
+
973
+ $node = $node->previousSibling;
974
+
975
+ continue;
976
+ }
977
+
978
+ if ($element->matches($selector)) {
979
+ $result[] = $element;
980
+ }
981
+
982
+ $node = $node->previousSibling;
983
+ }
984
+
985
+ return array_reverse($result);
986
+ }
987
+
988
+ /**
989
+ * @param string|null $selector
990
+ * @param string|null $nodeType
991
+ *
992
+ * @return \DiDom\Element|null
993
+ *
994
+ * @throws \InvalidArgumentException if node type is not string
995
+ * @throws \RuntimeException if node type is invalid
996
+ * @throws \LogicException if selector used with non DOMElement node type
997
+ */
998
+ public function nextSibling($selector = null, $nodeType = null)
999
+ {
1000
+ if ($this->node->nextSibling === null) {
1001
+ return null;
1002
+ }
1003
+
1004
+ if ($selector === null and $nodeType === null) {
1005
+ return new Element($this->node->nextSibling);
1006
+ }
1007
+
1008
+ if ($selector !== null and $nodeType === null) {
1009
+ $nodeType = 'DOMElement';
1010
+ }
1011
+
1012
+ if (!is_string($nodeType)) {
1013
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string, %s given', __METHOD__, gettype($nodeType)));
1014
+ }
1015
+
1016
+ $allowedTypes = ['DOMElement', 'DOMText', 'DOMComment'];
1017
+
1018
+ if (!in_array($nodeType, $allowedTypes)) {
1019
+ throw new RuntimeException(sprintf('Unknown node type "%s". Allowed types: %s', $nodeType, implode(', ', $allowedTypes)));
1020
+ }
1021
+
1022
+ if ($selector !== null and $nodeType !== 'DOMElement') {
1023
+ throw new LogicException(sprintf('Selector can be used only with DOMElement node type, %s given', $nodeType));
1024
+ }
1025
+
1026
+ $node = $this->node->nextSibling;
1027
+
1028
+ while ($node !== null) {
1029
+ if (get_class($node) !== $nodeType) {
1030
+ $node = $node->nextSibling;
1031
+
1032
+ continue;
1033
+ }
1034
+
1035
+ $element = new Element($node);
1036
+
1037
+ if ($selector === null) {
1038
+ return $element;
1039
+ }
1040
+
1041
+ if ($element->matches($selector)) {
1042
+ return $element;
1043
+ }
1044
+
1045
+ $node = $node->nextSibling;
1046
+ }
1047
+
1048
+ return null;
1049
+ }
1050
+
1051
+ /**
1052
+ * @param string|null $selector
1053
+ * @param string $nodeType
1054
+ *
1055
+ * @return \DiDom\Element[]
1056
+ *
1057
+ * @throws \InvalidArgumentException if node type is not string
1058
+ * @throws \RuntimeException if node type is invalid
1059
+ * @throws \LogicException if selector used with non DOMElement node type
1060
+ */
1061
+ public function nextSiblings($selector = null, $nodeType = null)
1062
+ {
1063
+ if ($this->node->nextSibling === null) {
1064
+ return [];
1065
+ }
1066
+
1067
+ if ($selector !== null and $nodeType === null) {
1068
+ $nodeType = 'DOMElement';
1069
+ }
1070
+
1071
+ if ($nodeType !== null) {
1072
+ if (!is_string($nodeType)) {
1073
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string, %s given', __METHOD__, gettype($nodeType)));
1074
+ }
1075
+
1076
+ $allowedTypes = ['DOMElement', 'DOMText', 'DOMComment'];
1077
+
1078
+ if (!in_array($nodeType, $allowedTypes)) {
1079
+ throw new RuntimeException(sprintf('Unknown node type "%s". Allowed types: %s', $nodeType, implode(', ', $allowedTypes)));
1080
+ }
1081
+ }
1082
+
1083
+ if ($selector !== null and $nodeType !== 'DOMElement') {
1084
+ throw new LogicException(sprintf('Selector can be used only with DOMElement node type, %s given', $nodeType));
1085
+ }
1086
+
1087
+ $result = [];
1088
+
1089
+ $node = $this->node->nextSibling;
1090
+
1091
+ while ($node !== null) {
1092
+ $element = new Element($node);
1093
+
1094
+ if ($nodeType === null) {
1095
+ $result[] = $element;
1096
+
1097
+ $node = $node->nextSibling;
1098
+
1099
+ continue;
1100
+ }
1101
+
1102
+ if (get_class($node) !== $nodeType) {
1103
+ $node = $node->nextSibling;
1104
+
1105
+ continue;
1106
+ }
1107
+
1108
+ if ($selector === null) {
1109
+ $result[] = $element;
1110
+
1111
+ $node = $node->nextSibling;
1112
+
1113
+ continue;
1114
+ }
1115
+
1116
+ if ($element->matches($selector)) {
1117
+ $result[] = $element;
1118
+ }
1119
+
1120
+ $node = $node->nextSibling;
1121
+ }
1122
+
1123
+ return $result;
1124
+ }
1125
+
1126
+ /**
1127
+ * @return \DiDom\Element|null
1128
+ */
1129
+ public function child($index)
1130
+ {
1131
+ $child = $this->node->childNodes->item($index);
1132
+
1133
+ return $child === null ? null : new Element($child);
1134
+ }
1135
+
1136
+ /**
1137
+ * @return \DiDom\Element|null
1138
+ */
1139
+ public function firstChild()
1140
+ {
1141
+ if ($this->node->firstChild === null) {
1142
+ return null;
1143
+ }
1144
+
1145
+ return new Element($this->node->firstChild);
1146
+ }
1147
+
1148
+ /**
1149
+ * @return \DiDom\Element|null
1150
+ */
1151
+ public function lastChild()
1152
+ {
1153
+ if ($this->node->lastChild === null) {
1154
+ return null;
1155
+ }
1156
+
1157
+ return new Element($this->node->lastChild);
1158
+ }
1159
+
1160
+ /**
1161
+ * @return bool
1162
+ */
1163
+ public function hasChildren()
1164
+ {
1165
+ return $this->node->hasChildNodes();
1166
+ }
1167
+
1168
+ /**
1169
+ * @return \DiDom\Element[]
1170
+ */
1171
+ public function children()
1172
+ {
1173
+ $children = [];
1174
+
1175
+ foreach ($this->node->childNodes as $node) {
1176
+ $children[] = new Element($node);
1177
+ }
1178
+
1179
+ return $children;
1180
+ }
1181
+
1182
+ /**
1183
+ * Removes child from list of children.
1184
+ *
1185
+ * @param \DOMNode|\DiDom\Element $childNode
1186
+ *
1187
+ * @return \DiDom\Element the node that has been removed
1188
+ */
1189
+ public function removeChild($childNode)
1190
+ {
1191
+ if ($childNode instanceof Element) {
1192
+ $childNode = $childNode->getNode();
1193
+ }
1194
+
1195
+ if (!$childNode instanceof DOMNode) {
1196
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s or DOMNode, %s given', __METHOD__, __CLASS__, (is_object($childNode) ? get_class($childNode) : gettype($childNode))));
1197
+ }
1198
+
1199
+ $removedNode = $this->node->removeChild($childNode);
1200
+
1201
+ return new Element($removedNode);
1202
+ }
1203
+
1204
+ /**
1205
+ * Removes all child nodes.
1206
+ *
1207
+ * @return \DiDom\Element[] the nodes that has been removed
1208
+ */
1209
+ public function removeChildren()
1210
+ {
1211
+ // we need to collect child nodes to array
1212
+ // because removing nodes from the DOMNodeList on iterating is not working
1213
+ $childNodes = [];
1214
+
1215
+ foreach ($this->node->childNodes as $childNode) {
1216
+ $childNodes[] = $childNode;
1217
+ }
1218
+
1219
+ $removedNodes = [];
1220
+
1221
+ foreach ($childNodes as $childNode) {
1222
+ $removedNode = $this->node->removeChild($childNode);
1223
+
1224
+ $removedNodes[] = new Element($removedNode);
1225
+ }
1226
+
1227
+ return $removedNodes;
1228
+ }
1229
+
1230
+ /**
1231
+ * Removes current node from the parent.
1232
+ *
1233
+ * @return \DiDom\Element the node that has been removed
1234
+ *
1235
+ * @throws \LogicException if current node has no parent node
1236
+ */
1237
+ public function remove()
1238
+ {
1239
+ if ($this->node->parentNode === null) {
1240
+ throw new LogicException('Can not remove element without parent node');
1241
+ }
1242
+
1243
+ $removedNode = $this->node->parentNode->removeChild($this->node);
1244
+
1245
+ return new Element($removedNode);
1246
+ }
1247
+
1248
+ /**
1249
+ * Replaces a child.
1250
+ *
1251
+ * @param \DOMNode|\DiDom\Element $newNode The new node
1252
+ * @param bool $clone Clone the node if true, otherwise move it
1253
+ *
1254
+ * @return \DiDom\Element The node that has been replaced
1255
+ *
1256
+ * @throws \LogicException if current node has no parent node
1257
+ */
1258
+ public function replace($newNode, $clone = true)
1259
+ {
1260
+ if ($this->node->parentNode === null) {
1261
+ throw new LogicException('Can not replace element without parent node');
1262
+ }
1263
+
1264
+ if ($newNode instanceof Element) {
1265
+ $newNode = $newNode->getNode();
1266
+ }
1267
+
1268
+ if (!$newNode instanceof DOMNode) {
1269
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of %s or DOMNode, %s given', __METHOD__, __CLASS__, (is_object($newNode) ? get_class($newNode) : gettype($newNode))));
1270
+ }
1271
+
1272
+ if ($clone) {
1273
+ $newNode = $newNode->cloneNode(true);
1274
+ }
1275
+
1276
+ if ($newNode->ownerDocument === null or !$this->getDocument()->is($newNode->ownerDocument)) {
1277
+ $newNode = $this->node->ownerDocument->importNode($newNode, true);
1278
+ }
1279
+
1280
+ $node = $this->node->parentNode->replaceChild($newNode, $this->node);
1281
+
1282
+ return new Element($node);
1283
+ }
1284
+
1285
+ /**
1286
+ * Get line number for a node.
1287
+ *
1288
+ * @return int
1289
+ */
1290
+ public function getLineNo()
1291
+ {
1292
+ return $this->node->getLineNo();
1293
+ }
1294
+
1295
+ /**
1296
+ * Clones a node.
1297
+ *
1298
+ * @param bool $deep Indicates whether to copy all descendant nodes
1299
+ *
1300
+ * @return \DiDom\Element The cloned node
1301
+ */
1302
+ public function cloneNode($deep = true)
1303
+ {
1304
+ return new Element($this->node->cloneNode($deep));
1305
+ }
1306
+
1307
+ /**
1308
+ * Sets current node instance.
1309
+ *
1310
+ * @param \DOMElement|\DOMText|\DOMComment $node
1311
+ *
1312
+ * @return \DiDom\Element
1313
+ */
1314
+ protected function setNode($node)
1315
+ {
1316
+ $allowedClasses = ['DOMElement', 'DOMText', 'DOMComment'];
1317
+
1318
+ if (!in_array(get_class($node), $allowedClasses)) {
1319
+ throw new InvalidArgumentException(sprintf('Argument 1 passed to %s must be an instance of DOMElement, DOMText or DOMComment, %s given', __METHOD__, (is_object($node) ? get_class($node) : gettype($node))));
1320
+ }
1321
+
1322
+ $this->node = $node;
1323
+
1324
+ return $this;
1325
+ }
1326
+
1327
+ /**
1328
+ * Returns current node instance.
1329
+ *
1330
+ * @return \DOMElement|\DOMText|\DOMComment
1331
+ */
1332
+ public function getNode()
1333
+ {
1334
+ return $this->node;
1335
+ }
1336
+
1337
+ /**
1338
+ * Returns the document associated with this node.
1339
+ *
1340
+ * @return \DiDom\Document|null
1341
+ */
1342
+ public function getDocument()
1343
+ {
1344
+ if ($this->node->ownerDocument === null) {
1345
+ return null;
1346
+ }
1347
+
1348
+ return new Document($this->node->ownerDocument);
1349
+ }
1350
+
1351
+ /**
1352
+ * Get the DOM document with the current element.
1353
+ *
1354
+ * @param string $encoding The document encoding
1355
+ *
1356
+ * @return \DiDom\Document
1357
+ */
1358
+ public function toDocument($encoding = 'UTF-8')
1359
+ {
1360
+ $document = new Document(null, false, $encoding);
1361
+
1362
+ $document->appendChild($this->node);
1363
+
1364
+ return $document;
1365
+ }
1366
+
1367
+ /**
1368
+ * Dynamically set an attribute on the element.
1369
+ *
1370
+ * @param string $name The attribute name
1371
+ * @param mixed $value The attribute value
1372
+ *
1373
+ * @return \DiDom\Element
1374
+ */
1375
+ public function __set($name, $value)
1376
+ {
1377
+ return $this->setAttribute($name, $value);
1378
+ }
1379
+
1380
+ /**
1381
+ * Dynamically access the element's attributes.
1382
+ *
1383
+ * @param string $name The attribute name
1384
+ *
1385
+ * @return string|null
1386
+ */
1387
+ public function __get($name)
1388
+ {
1389
+ switch ($name) {
1390
+ case 'tag':
1391
+ return $this->node->tagName;
1392
+ break;
1393
+ default:
1394
+ return $this->getAttribute($name);
1395
+ }
1396
+ }
1397
+
1398
+ /**
1399
+ * Determine if an attribute exists on the element.
1400
+ *
1401
+ * @param string $name The attribute name
1402
+ *
1403
+ * @return bool
1404
+ */
1405
+ public function __isset($name)
1406
+ {
1407
+ return $this->hasAttribute($name);
1408
+ }
1409
+
1410
+ /**
1411
+ * Unset an attribute on the model.
1412
+ *
1413
+ * @param string $name The attribute name
1414
+ */
1415
+ public function __unset($name)
1416
+ {
1417
+ $this->removeAttribute($name);
1418
+ }
1419
+
1420
+ /**
1421
+ * Convert the element to its string representation.
1422
+ *
1423
+ * @return string
1424
+ */
1425
+ public function __toString()
1426
+ {
1427
+ return $this->html();
1428
+ }
1429
+
1430
+ /**
1431
+ * Searches for an node in the DOM tree for a given XPath expression or a CSS selector.
1432
+ *
1433
+ * @param string $expression XPath expression or a CSS selector
1434
+ * @param string $type The type of the expression
1435
+ * @param bool $wrapNode Returns array of \DiDom\Element if true, otherwise array of \DOMElement
1436
+ *
1437
+ * @return \DiDom\Element[]|\DOMElement[]
1438
+ *
1439
+ * @deprecated Not longer recommended, use Element::find() instead.
1440
+ */
1441
+ public function __invoke($expression, $type = Query::TYPE_CSS, $wrapNode = true)
1442
+ {
1443
+ return $this->find($expression, $type, $wrapNode);
1444
+ }
1445
+ }
vendor/imangazaliev/didom/src/DiDom/Encoder.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ class Encoder
6
+ {
7
+ /**
8
+ * @param string $string
9
+ * @param string $encoding
10
+ *
11
+ * @return string
12
+ */
13
+ public static function convertToHtmlEntities($string, $encoding)
14
+ {
15
+ if (function_exists('mb_convert_encoding')) {
16
+ return mb_convert_encoding($string, 'HTML-ENTITIES', $encoding);
17
+ }
18
+
19
+ if ('UTF-8' !== $encoding) {
20
+ $string = iconv($encoding, 'UTF-8//IGNORE', $string);
21
+ }
22
+
23
+ return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'htmlEncodingCallback'], $string);
24
+ }
25
+
26
+ /**
27
+ * @param string[] $matches
28
+ *
29
+ * @return string
30
+ */
31
+ private static function htmlEncodingCallback($matches)
32
+ {
33
+ $characterIndex = 1;
34
+ $entities = '';
35
+
36
+ $codes = unpack('C*', htmlentities($matches[0], ENT_COMPAT, 'UTF-8'));
37
+
38
+ while (isset($codes[$characterIndex])) {
39
+ if (0x80 > $codes[$characterIndex]) {
40
+ $entities .= chr($codes[$characterIndex++]);
41
+
42
+ continue;
43
+ }
44
+
45
+ if (0xF0 <= $codes[$characterIndex]) {
46
+ $code = (($codes[$characterIndex++] - 0xF0) << 18) + (($codes[$characterIndex++] - 0x80) << 12) + (($codes[$characterIndex++] - 0x80) << 6) + $codes[$characterIndex++] - 0x80;
47
+ } elseif (0xE0 <= $codes[$characterIndex]) {
48
+ $code = (($codes[$characterIndex++] - 0xE0) << 12) + (($codes[$characterIndex++] - 0x80) << 6) + $codes[$characterIndex++] - 0x80;
49
+ } else {
50
+ $code = (($codes[$characterIndex++] - 0xC0) << 6) + $codes[$characterIndex++] - 0x80;
51
+ }
52
+
53
+ $entities .= '&#'.$code.';';
54
+ }
55
+
56
+ return $entities;
57
+ }
58
+ }
vendor/imangazaliev/didom/src/DiDom/Errors.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ class Errors
6
+ {
7
+ /**
8
+ * @var bool
9
+ */
10
+ protected static $internalErrors;
11
+
12
+ /**
13
+ * @var bool
14
+ */
15
+ protected static $disableEntities;
16
+
17
+ /**
18
+ * Disable error reporting.
19
+ */
20
+ public static function disable()
21
+ {
22
+ self::$internalErrors = libxml_use_internal_errors(true);
23
+ self::$disableEntities = libxml_disable_entity_loader(true);
24
+ }
25
+
26
+ /**
27
+ * Restore error reporting.
28
+ *
29
+ * @param bool $clear
30
+ */
31
+ public static function restore($clear = true)
32
+ {
33
+ if ($clear) {
34
+ libxml_clear_errors();
35
+ }
36
+
37
+ libxml_use_internal_errors(self::$internalErrors);
38
+ libxml_disable_entity_loader(self::$disableEntities);
39
+ }
40
+ }
vendor/imangazaliev/didom/src/DiDom/Exceptions/InvalidSelectorException.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom\Exceptions;
4
+
5
+ use Exception;
6
+
7
+ class InvalidSelectorException extends Exception
8
+ {
9
+ //
10
+ }
vendor/imangazaliev/didom/src/DiDom/Query.php ADDED
@@ -0,0 +1,559 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ use DiDom\Exceptions\InvalidSelectorException;
6
+ use InvalidArgumentException;
7
+ use RuntimeException;
8
+
9
+ class Query
10
+ {
11
+ /**
12
+ * Types of expression.
13
+ *
14
+ * @const string
15
+ */
16
+ const TYPE_XPATH = 'XPATH';
17
+ const TYPE_CSS = 'CSS';
18
+
19
+ /**
20
+ * @var array
21
+ */
22
+ protected static $compiled = [];
23
+
24
+ /**
25
+ * Converts a CSS selector into an XPath expression.
26
+ *
27
+ * @param string $expression XPath expression or CSS selector
28
+ * @param string $type The type of the expression
29
+ *
30
+ * @return string XPath expression
31
+ *
32
+ * @throws InvalidSelectorException if the expression is empty
33
+ */
34
+ public static function compile($expression, $type = self::TYPE_CSS)
35
+ {
36
+ if (!is_string($expression)) {
37
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, gettype($expression)));
38
+ }
39
+
40
+ if (!is_string($type)) {
41
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string, %s given', __METHOD__, gettype($type)));
42
+ }
43
+
44
+ if (strcasecmp($type, self::TYPE_XPATH) !== 0 and strcasecmp($type, self::TYPE_CSS) !== 0) {
45
+ throw new RuntimeException(sprintf('Unknown expression type "%s"', $type));
46
+ }
47
+
48
+ $expression = trim($expression);
49
+
50
+ if ($expression === '') {
51
+ throw new InvalidSelectorException('The expression must not be empty');
52
+ }
53
+
54
+ if (strcasecmp($type, self::TYPE_XPATH) === 0) {
55
+ return $expression;
56
+ }
57
+
58
+ if (!array_key_exists($expression, static::$compiled)) {
59
+ static::$compiled[$expression] = static::cssToXpath($expression);
60
+ }
61
+
62
+ return static::$compiled[$expression];
63
+ }
64
+
65
+ /**
66
+ * Converts a CSS selector into an XPath expression.
67
+ *
68
+ * @param string $selector A CSS selector
69
+ * @param string $prefix Specifies the nesting of nodes
70
+ *
71
+ * @return string XPath expression
72
+ */
73
+ public static function cssToXpath($selector, $prefix = '//')
74
+ {
75
+ $paths = [];
76
+
77
+ while ($selector !== '') {
78
+ list($xpath, $selector) = static::parseAndConvertSelector($selector, $prefix);
79
+
80
+ if (substr($selector, 0, 1) === ',') {
81
+ $selector = trim($selector, ', ');
82
+ }
83
+
84
+ $paths[] = $xpath;
85
+ }
86
+
87
+ return implode('|', $paths);
88
+ }
89
+
90
+ /**
91
+ * @param string $selector
92
+ * @param string $prefix
93
+ *
94
+ * @return array
95
+ */
96
+ protected static function parseAndConvertSelector($selector, $prefix = '//')
97
+ {
98
+ if (substr($selector, 0, 1) === '>') {
99
+ $prefix = '/';
100
+
101
+ $selector = ltrim($selector, '> ');
102
+ }
103
+
104
+ $segments = self::getSegments($selector);
105
+ $xpath = '';
106
+
107
+ while (count($segments) > 0) {
108
+ $xpath .= self::buildXpath($segments, $prefix);
109
+
110
+ $selector = trim(substr($selector, strlen($segments['selector'])));
111
+ $prefix = isset($segments['rel']) ? '/' : '//';
112
+
113
+ if ($selector === '' or substr($selector, 0, 2) === '::' or substr($selector, 0, 1) === ',') {
114
+ break;
115
+ }
116
+
117
+ $segments = self::getSegments($selector);
118
+ }
119
+
120
+ // if selector has property
121
+ if (substr($selector, 0, 2) === '::') {
122
+ $property = self::parseProperty($selector);
123
+ $propertyXpath = self::convertProperty($property['name'], $property['args']);
124
+
125
+ $selector = substr($selector, strlen($property['property']));
126
+ $selector = trim($selector);
127
+
128
+ $xpath .= '/' . $propertyXpath;
129
+ }
130
+
131
+ return [$xpath, $selector];
132
+ }
133
+
134
+ /**
135
+ * @param string $selector
136
+ *
137
+ * @return array
138
+ *
139
+ * @throws InvalidSelectorException
140
+ */
141
+ protected static function parseProperty($selector)
142
+ {
143
+ $name = '(?P<name>[\w\-]+)';
144
+ $args = '(?:\((?P<args>[^\)]+)?\))?';
145
+
146
+ $regexp = '/^::'.$name.$args.'/is';
147
+
148
+ if (preg_match($regexp, $selector, $matches) !== 1) {
149
+ throw new InvalidSelectorException(sprintf('Invalid property "%s"', $selector));
150
+ }
151
+
152
+ $result = [];
153
+
154
+ $result['property'] = $matches[0];
155
+ $result['name'] = $matches['name'];
156
+ $result['args'] = isset($matches['args']) ? explode(',', $matches['args']) : [];
157
+
158
+ $result['args'] = array_map('trim', $result['args']);
159
+
160
+ return $result;
161
+ }
162
+
163
+ /**
164
+ * @param string $name
165
+ * @param array $args
166
+ *
167
+ * @return string
168
+ *
169
+ * @throws InvalidSelectorException if the passed property is unknown
170
+ */
171
+ protected static function convertProperty($name, array $args = [])
172
+ {
173
+ if ($name === 'text') {
174
+ return 'text()';
175
+ }
176
+
177
+ if ($name === 'attr') {
178
+ if (count($args) === 0) {
179
+ return '@*';
180
+ }
181
+
182
+ $attributes = [];
183
+
184
+ foreach ($args as $attribute) {
185
+ $attributes[] = sprintf('name() = "%s"', $attribute);
186
+ }
187
+
188
+ return sprintf('@*[%s]', implode(' or ', $attributes));
189
+ }
190
+
191
+ throw new InvalidSelectorException(sprintf('Unknown property "%s"', $name));
192
+ }
193
+
194
+ /**
195
+ * Converts a CSS pseudo-class into an XPath expression.
196
+ *
197
+ * @param string $pseudo Pseudo-class
198
+ * @param string $tagName
199
+ * @param array $parameters
200
+ *
201
+ * @return string
202
+ *
203
+ * @throws InvalidSelectorException if passed an unknown pseudo-class
204
+ */
205
+ protected static function convertPseudo($pseudo, &$tagName, array $parameters = [])
206
+ {
207
+ switch ($pseudo) {
208
+ case 'first-child':
209
+ return 'position() = 1';
210
+ break;
211
+ case 'last-child':
212
+ return 'position() = last()';
213
+ break;
214
+ case 'nth-child':
215
+ $xpath = sprintf('(name()="%s") and (%s)', $tagName, self::convertNthExpression($parameters[0]));
216
+ $tagName = '*';
217
+
218
+ return $xpath;
219
+ break;
220
+ case 'contains':
221
+ $string = trim($parameters[0], '\'"');
222
+
223
+ if (count($parameters) === 1) {
224
+ return self::convertContains($string);
225
+ }
226
+
227
+ if ($parameters[1] !== 'true' and $parameters[1] !== 'false') {
228
+ throw new InvalidSelectorException(sprintf('Parameter 2 of "contains" pseudo-class should be equal true or false, "%s" given', $parameters[1]));
229
+ }
230
+
231
+ $caseSensitive = $parameters[1] === 'true';
232
+
233
+ if (count($parameters) === 2) {
234
+ return self::convertContains($string, $caseSensitive);
235
+ }
236
+
237
+ if ($parameters[2] !== 'true' and $parameters[2] !== 'false') {
238
+ throw new InvalidSelectorException(sprintf('Parameter 3 of "contains" pseudo-class should be equal true or false, "%s" given', $parameters[2]));
239
+ }
240
+
241
+ $fullMatch = $parameters[2] === 'true';
242
+
243
+ return self::convertContains($string, $caseSensitive, $fullMatch);
244
+ break;
245
+ case 'has':
246
+ return self::cssToXpath($parameters[0], './/');
247
+ break;
248
+ case 'not':
249
+ return sprintf('not(self::%s)', self::cssToXpath($parameters[0], ''));
250
+ break;
251
+ case 'nth-of-type':
252
+ return self::convertNthExpression($parameters[0]);
253
+ break;
254
+ case 'empty':
255
+ return 'count(descendant::*) = 0';
256
+ break;
257
+ case 'not-empty':
258
+ return 'count(descendant::*) > 0';
259
+ break;
260
+ }
261
+
262
+ throw new InvalidSelectorException(sprintf('Unknown pseudo-class "%s"', $pseudo));
263
+ }
264
+
265
+ /**
266
+ * @param array $segments
267
+ * @param string $prefix Specifies the nesting of nodes
268
+ *
269
+ * @return string XPath expression
270
+ *
271
+ * @throws InvalidArgumentException if you neither specify tag name nor attributes
272
+ */
273
+ public static function buildXpath(array $segments, $prefix = '//')
274
+ {
275
+ $tagName = isset($segments['tag']) ? $segments['tag'] : '*';
276
+
277
+ $attributes = [];
278
+
279
+ // if the id attribute specified
280
+ if (isset($segments['id'])) {
281
+ $attributes[] = sprintf('@id="%s"', $segments['id']);
282
+ }
283
+
284
+ // if the class attribute specified
285
+ if (isset($segments['classes'])) {
286
+ foreach ($segments['classes'] as $class) {
287
+ $attributes[] = sprintf('contains(concat(" ", normalize-space(@class), " "), " %s ")', $class);
288
+ }
289
+ }
290
+
291
+ // if the attributes specified
292
+ if (isset($segments['attributes'])) {
293
+ foreach ($segments['attributes'] as $name => $value) {
294
+ $attributes[] = self::convertAttribute($name, $value);
295
+ }
296
+ }
297
+
298
+ // if the pseudo class specified
299
+ if (isset($segments['pseudo'])) {
300
+ $expression = isset($segments['expr']) ? trim($segments['expr']) : '';
301
+
302
+ $parameters = explode(',', $expression);
303
+ $parameters = array_map('trim', $parameters);
304
+
305
+ $attributes[] = self::convertPseudo($segments['pseudo'], $tagName, $parameters);
306
+ }
307
+
308
+ if (count($attributes) === 0 and !isset($segments['tag'])) {
309
+ throw new InvalidArgumentException('The array of segments should contain the name of the tag or at least one attribute');
310
+ }
311
+
312
+ $xpath = $prefix.$tagName;
313
+
314
+ if ($count = count($attributes)) {
315
+ $xpath .= ($count > 1) ? sprintf('[(%s)]', implode(') and (', $attributes)) : sprintf('[%s]', $attributes[0]);
316
+ }
317
+
318
+ return $xpath;
319
+ }
320
+
321
+ /**
322
+ * @param string $name The attribute name
323
+ * @param string $value The attribute value
324
+ *
325
+ * @return string
326
+ */
327
+ protected static function convertAttribute($name, $value)
328
+ {
329
+ $isSimpleSelector = !in_array(substr($name, 0, 1), ['^', '!']);
330
+ $isSimpleSelector = $isSimpleSelector && (!in_array(substr($name, -1), ['^', '$', '*', '!', '~']));
331
+
332
+ if ($isSimpleSelector) {
333
+ // if specified only the attribute name
334
+ $xpath = $value === null ? '@'.$name : sprintf('@%s="%s"', $name, $value);
335
+
336
+ return $xpath;
337
+ }
338
+
339
+ // if the attribute name starts with ^
340
+ // example: *[^data-]
341
+ if (substr($name, 0, 1) === '^') {
342
+ $xpath = sprintf('@*[starts-with(name(), "%s")]', substr($name, 1));
343
+
344
+ return $value === null ? $xpath : sprintf('%s="%s"', $xpath, $value);
345
+ }
346
+
347
+ // if the attribute name starts with !
348
+ // example: input[!disabled]
349
+ if (substr($name, 0, 1) === '!') {
350
+ $xpath = sprintf('not(@%s)', substr($name, 1));
351
+
352
+ return $xpath;
353
+ }
354
+
355
+ $symbol = substr($name, -1);
356
+ $name = substr($name, 0, -1);
357
+
358
+ switch ($symbol) {
359
+ case '^':
360
+ $xpath = sprintf('starts-with(@%s, "%s")', $name, $value);
361
+ break;
362
+ case '$':
363
+ $xpath = sprintf('substring(@%s, string-length(@%s) - string-length("%s") + 1) = "%s"', $name, $name, $value, $value);
364
+ break;
365
+ case '*':
366
+ $xpath = sprintf('contains(@%s, "%s")', $name, $value);
367
+ break;
368
+ case '!':
369
+ $xpath = sprintf('not(@%s="%s")', $name, $value);
370
+ break;
371
+ case '~':
372
+ $xpath = sprintf('contains(concat(" ", normalize-space(@%s), " "), " %s ")', $name, $value);
373
+ break;
374
+ }
375
+
376
+ return $xpath;
377
+ }
378
+
379
+ /**
380
+ * Converts nth-expression into an XPath expression.
381
+ *
382
+ * @param string $expression nth-expression
383
+ *
384
+ * @return string
385
+ *
386
+ * @throws InvalidSelectorException if passed nth-child is empty
387
+ * @throws InvalidSelectorException if passed an unknown nth-child expression
388
+ */
389
+ protected static function convertNthExpression($expression)
390
+ {
391
+ if ($expression === '') {
392
+ throw new InvalidSelectorException('nth-child (or nth-last-child) expression must not be empty');
393
+ }
394
+
395
+ if ($expression === 'odd') {
396
+ return 'position() mod 2 = 1 and position() >= 1';
397
+ }
398
+
399
+ if ($expression === 'even') {
400
+ return 'position() mod 2 = 0 and position() >= 0';
401
+ }
402
+
403
+ if (is_numeric($expression)) {
404
+ return sprintf('position() = %d', $expression);
405
+ }
406
+
407
+ if (preg_match("/^(?P<mul>[0-9]?n)(?:(?P<sign>\+|\-)(?P<pos>[0-9]+))?$/is", $expression, $segments)) {
408
+ if (isset($segments['mul'])) {
409
+ $multiplier = $segments['mul'] === 'n' ? 1 : trim($segments['mul'], 'n');
410
+ $sign = (isset($segments['sign']) and $segments['sign'] === '+') ? '-' : '+';
411
+ $position = isset($segments['pos']) ? $segments['pos'] : 0;
412
+
413
+ return sprintf('(position() %s %d) mod %d = 0 and position() >= %d', $sign, $position, $multiplier, $position);
414
+ }
415
+ }
416
+
417
+ throw new InvalidSelectorException(sprintf('Invalid nth-child expression "%s"', $expression));
418
+ }
419
+
420
+ /**
421
+ * @param string $string
422
+ * @param bool $caseSensitive
423
+ * @param bool $fullMatch
424
+ *
425
+ * @return string
426
+ */
427
+ protected static function convertContains($string, $caseSensitive = true, $fullMatch = false)
428
+ {
429
+ if ($caseSensitive and $fullMatch) {
430
+ return sprintf('text() = "%s"', $string);
431
+ }
432
+
433
+ if ($caseSensitive and !$fullMatch) {
434
+ return sprintf('contains(text(), "%s")', $string);
435
+ }
436
+
437
+ $strToLowerFunction = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower';
438
+
439
+ if (!$caseSensitive and $fullMatch) {
440
+ return sprintf("php:functionString(\"{$strToLowerFunction}\", .) = php:functionString(\"{$strToLowerFunction}\", \"%s\")", $string);
441
+ }
442
+
443
+ // if !$caseSensitive and !$fullMatch
444
+ return sprintf("contains(php:functionString(\"{$strToLowerFunction}\", .), php:functionString(\"{$strToLowerFunction}\", \"%s\"))", $string);
445
+ }
446
+
447
+ /**
448
+ * Splits the CSS selector into parts (tag name, ID, classes, attributes, pseudo-class).
449
+ *
450
+ * @param string $selector CSS selector
451
+ *
452
+ * @return array
453
+ *
454
+ * @throws InvalidSelectorException if the selector is empty or not valid
455
+ */
456
+ public static function getSegments($selector)
457
+ {
458
+ $selector = trim($selector);
459
+
460
+ if ($selector === '') {
461
+ throw new InvalidSelectorException('The selector must not be empty');
462
+ }
463
+
464
+ $tag = '(?P<tag>[\*|\w|\-]+)?';
465
+ $id = '(?:#(?P<id>[\w|\-]+))?';
466
+ $classes = '(?P<classes>\.[\w|\-|\.]+)*';
467
+ $attrs = '(?P<attrs>(?:\[.+?\])*)?';
468
+ $name = '(?P<pseudo>[\w\-]+)';
469
+ $expr = '(?:\((?P<expr>[^\)]+)\))';
470
+ $pseudo = '(?::'.$name.$expr.'?)?';
471
+ $rel = '\s*(?P<rel>>)?';
472
+
473
+ $regexp = '/'.$tag.$id.$classes.$attrs.$pseudo.$rel.'/is';
474
+
475
+ if (preg_match($regexp, $selector, $segments)) {
476
+ if ($segments[0] === '') {
477
+ throw new InvalidSelectorException(sprintf('Invalid selector "%s"', $selector));
478
+ }
479
+
480
+ $result['selector'] = $segments[0];
481
+
482
+ if (isset($segments['tag']) and $segments['tag'] !== '') {
483
+ $result['tag'] = $segments['tag'];
484
+ }
485
+
486
+ // if the id attribute specified
487
+ if (isset($segments['id']) and $segments['id'] !== '') {
488
+ $result['id'] = $segments['id'];
489
+ }
490
+
491
+ // if the attributes specified
492
+ if (isset($segments['attrs'])) {
493
+ $attributes = trim($segments['attrs'], '[]');
494
+ $attributes = explode('][', $attributes);
495
+
496
+ foreach ($attributes as $attribute) {
497
+ if ($attribute !== '') {
498
+ list($name, $value) = array_pad(explode('=', $attribute, 2), 2, null);
499
+
500
+ if ($name === '') {
501
+ throw new InvalidSelectorException(sprintf('Invalid selector "%s": attribute name must not be empty', $selector));
502
+ }
503
+
504
+ // equal null if specified only the attribute name
505
+ $result['attributes'][$name] = is_string($value) ? trim($value, '\'"') : null;
506
+ }
507
+ }
508
+ }
509
+
510
+ // if the class attribute specified
511
+ if (isset($segments['classes'])) {
512
+ $classes = trim($segments['classes'], '.');
513
+ $classes = explode('.', $classes);
514
+
515
+ foreach ($classes as $class) {
516
+ if ($class !== '') {
517
+ $result['classes'][] = $class;
518
+ }
519
+ }
520
+ }
521
+
522
+ // if the pseudo class specified
523
+ if (isset($segments['pseudo']) and $segments['pseudo'] !== '') {
524
+ $result['pseudo'] = $segments['pseudo'];
525
+
526
+ if (isset($segments['expr']) and $segments['expr'] !== '') {
527
+ $result['expr'] = $segments['expr'];
528
+ }
529
+ }
530
+
531
+ // if it is a direct descendant
532
+ if (isset($segments['rel'])) {
533
+ $result['rel'] = $segments['rel'];
534
+ }
535
+
536
+ return $result;
537
+ }
538
+
539
+ throw new InvalidSelectorException(sprintf('Invalid selector "%s"', $selector));
540
+ }
541
+
542
+ /**
543
+ * @return array
544
+ */
545
+ public static function getCompiled()
546
+ {
547
+ return static::$compiled;
548
+ }
549
+
550
+ /**
551
+ * @param array $compiled
552
+ *
553
+ * @throws \InvalidArgumentException if the attributes is not an array
554
+ */
555
+ public static function setCompiled(array $compiled)
556
+ {
557
+ static::$compiled = $compiled;
558
+ }
559
+ }
vendor/imangazaliev/didom/src/DiDom/StyleAttribute.php ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DiDom;
4
+
5
+ use InvalidArgumentException;
6
+
7
+ class StyleAttribute
8
+ {
9
+ /**
10
+ * The DOM element instance.
11
+ *
12
+ * @var \DiDom\Element
13
+ */
14
+ protected $element;
15
+
16
+ /**
17
+ * @var string
18
+ */
19
+ protected $styleString = '';
20
+
21
+ /**
22
+ * @var array
23
+ */
24
+ protected $properties = [];
25
+
26
+ /**
27
+ * Constructor.
28
+ *
29
+ * @param \DiDom\Element $element
30
+ *
31
+ * @throws \InvalidArgumentException if passed argument is not an element node
32
+ */
33
+ public function __construct(Element $element)
34
+ {
35
+ if (!$element->isElementNode()) {
36
+ throw new InvalidArgumentException(sprintf('The element should contain DOMElement node'));
37
+ }
38
+
39
+ $this->element = $element;
40
+
41
+ $this->parseStyleAttribute();
42
+ }
43
+
44
+ /**
45
+ * Parses style attribute of the element.
46
+ */
47
+ protected function parseStyleAttribute()
48
+ {
49
+ if (!$this->element->hasAttribute('style')) {
50
+ // possible if style attribute has been removed
51
+ if ($this->styleString !== '') {
52
+ $this->styleString = '';
53
+ $this->properties = [];
54
+ }
55
+
56
+ return;
57
+ }
58
+
59
+ // if style attribute is not changed
60
+ if ($this->element->getAttribute('style') === $this->styleString) {
61
+ return;
62
+ }
63
+
64
+ // save style attribute as is (without trimming)
65
+ $this->styleString = $this->element->getAttribute('style');
66
+
67
+ $styleString = trim($this->styleString, ' ;');
68
+
69
+ if ($styleString === '') {
70
+ $this->properties = [];
71
+
72
+ return;
73
+ }
74
+
75
+ $properties = explode(';', $styleString);
76
+
77
+ foreach ($properties as $property) {
78
+ list($name, $value) = explode(':', $property);
79
+
80
+ $name = trim($name);
81
+ $value = trim($value);
82
+
83
+ $this->properties[$name] = $value;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Updates style attribute of the element.
89
+ */
90
+ protected function updateStyleAttribute()
91
+ {
92
+ $this->styleString = $this->buildStyleString();
93
+
94
+ $this->element->setAttribute('style', $this->styleString);
95
+ }
96
+
97
+ /**
98
+ * @return string
99
+ */
100
+ protected function buildStyleString()
101
+ {
102
+ $properties = [];
103
+
104
+ foreach ($this->properties as $propertyName => $value) {
105
+ $properties[] = $propertyName.': '.$value;
106
+ }
107
+
108
+ return implode('; ', $properties);
109
+ }
110
+
111
+ /**
112
+ * @param string $name
113
+ * @param string $value
114
+ *
115
+ * @return \DiDom\StyleAttribute
116
+ *
117
+ * @throws \InvalidArgumentException if property name is not a string
118
+ * @throws \InvalidArgumentException if property value is not a string
119
+ */
120
+ public function setProperty($name, $value)
121
+ {
122
+ if (!is_string($name)) {
123
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($name) ? get_class($name) : gettype($name))));
124
+ }
125
+
126
+ if (!is_string($value)) {
127
+ throw new InvalidArgumentException(sprintf('%s expects parameter 2 to be string, %s given', __METHOD__, (is_object($value) ? get_class($value) : gettype($value))));
128
+ }
129
+
130
+ $this->parseStyleAttribute();
131
+
132
+ $this->properties[$name] = $value;
133
+
134
+ $this->updateStyleAttribute();
135
+
136
+ return $this;
137
+ }
138
+
139
+ /**
140
+ * @param array $properties
141
+ *
142
+ * @return \DiDom\StyleAttribute
143
+ *
144
+ * @throws \InvalidArgumentException if property name is not a string
145
+ * @throws \InvalidArgumentException if property value is not a string
146
+ */
147
+ public function setMultipleProperties(array $properties)
148
+ {
149
+ $this->parseStyleAttribute();
150
+
151
+ foreach ($properties as $propertyName => $value) {
152
+ if (!is_string($propertyName)) {
153
+ throw new InvalidArgumentException(sprintf('Property name must be a string, %s given', (is_object($propertyName) ? get_class($propertyName) : gettype($propertyName))));
154
+ }
155
+
156
+ if (!is_string($value)) {
157
+ throw new InvalidArgumentException(sprintf('Property value must be a string, %s given', (is_object($value) ? get_class($value) : gettype($value))));
158
+ }
159
+
160
+ $this->properties[$propertyName] = $value;
161
+ }
162
+
163
+ $this->updateStyleAttribute();
164
+
165
+ return $this;
166
+ }
167
+
168
+ /**
169
+ * @param string $name
170
+ * @param mixed $default
171
+ *
172
+ * @return mixed
173
+ */
174
+ public function getProperty($name, $default = null)
175
+ {
176
+ if (!is_string($name)) {
177
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($name) ? get_class($name) : gettype($name))));
178
+ }
179
+
180
+ $this->parseStyleAttribute();
181
+
182
+ if (!array_key_exists($name, $this->properties)) {
183
+ return $default;
184
+ }
185
+
186
+ return $this->properties[$name];
187
+ }
188
+
189
+ /**
190
+ * @param array $propertyNames
191
+ *
192
+ * @return mixed
193
+ *
194
+ * @throws \InvalidArgumentException if property name is not a string
195
+ */
196
+ public function getMultipleProperties(array $propertyNames)
197
+ {
198
+ $this->parseStyleAttribute();
199
+
200
+ $result = [];
201
+
202
+ foreach ($propertyNames as $propertyName) {
203
+ if (!is_string($propertyName)) {
204
+ throw new InvalidArgumentException(sprintf('Property name must be a string, %s given', (is_object($propertyName) ? get_class($propertyName) : gettype($propertyName))));
205
+ }
206
+
207
+ if (array_key_exists($propertyName, $this->properties)) {
208
+ $result[$propertyName] = $this->properties[$propertyName];
209
+ }
210
+ }
211
+
212
+ return $result;
213
+ }
214
+
215
+ /**
216
+ * @return array
217
+ */
218
+ public function getAllProperties()
219
+ {
220
+ $this->parseStyleAttribute();
221
+
222
+ return $this->properties;
223
+ }
224
+
225
+ /**
226
+ * @param string $name
227
+ *
228
+ * @return bool
229
+ */
230
+ public function hasProperty($name)
231
+ {
232
+ if (!is_string($name)) {
233
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($name) ? get_class($name) : gettype($name))));
234
+ }
235
+
236
+ $this->parseStyleAttribute();
237
+
238
+ return array_key_exists($name, $this->properties);
239
+ }
240
+
241
+ /**
242
+ * @param string $name
243
+ *
244
+ * @return \DiDom\StyleAttribute
245
+ *
246
+ * @throws \InvalidArgumentException if property name is not a string
247
+ */
248
+ public function removeProperty($name)
249
+ {
250
+ if (!is_string($name)) {
251
+ throw new InvalidArgumentException(sprintf('%s expects parameter 1 to be string, %s given', __METHOD__, (is_object($name) ? get_class($name) : gettype($name))));
252
+ }
253
+
254
+ $this->parseStyleAttribute();
255
+
256
+ unset($this->properties[$name]);
257
+
258
+ $this->updateStyleAttribute();
259
+
260
+ return $this;
261
+ }
262
+
263
+ /**
264
+ * @param array $propertyNames
265
+ *
266
+ * @return \DiDom\StyleAttribute
267
+ *
268
+ * @throws \InvalidArgumentException if property name is not a string
269
+ */
270
+ public function removeMultipleProperties(array $propertyNames)
271
+ {
272
+ $this->parseStyleAttribute();
273
+
274
+ foreach ($propertyNames as $propertyName) {
275
+ if (!is_string($propertyName)) {
276
+ throw new InvalidArgumentException(sprintf('Property name must be a string, %s given', (is_object($propertyName) ? get_class($propertyName) : gettype($propertyName))));
277
+ }
278
+
279
+ unset($this->properties[$propertyName]);
280
+ }
281
+
282
+ $this->updateStyleAttribute();
283
+
284
+ return $this;
285
+ }
286
+
287
+ /**
288
+ * @param string[] $exclusions
289
+ *
290
+ * @return \DiDom\StyleAttribute
291
+ */
292
+ public function removeAllProperties(array $exclusions = [])
293
+ {
294
+ $this->parseStyleAttribute();
295
+
296
+ $preservedProperties = [];
297
+
298
+ foreach ($exclusions as $propertyName) {
299
+ if (!is_string($propertyName)) {
300
+ throw new InvalidArgumentException(sprintf('Property name must be a string, %s given', (is_object($propertyName) ? get_class($propertyName) : gettype($propertyName))));
301
+ }
302
+
303
+ if (!array_key_exists($propertyName, $this->properties)) {
304
+ continue;
305
+ }
306
+
307
+ $preservedProperties[$propertyName] = $this->properties[$propertyName];
308
+ }
309
+
310
+ $this->properties = $preservedProperties;
311
+
312
+ $this->updateStyleAttribute();
313
+
314
+ return $this;
315
+ }
316
+
317
+ /**
318
+ * @return \DiDom\Element
319
+ */
320
+ public function getElement()
321
+ {
322
+ return $this->element;
323
+ }
324
+ }
vendor/imangazaliev/didom/tests/DiDom/ClassAttributeTest.php ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Tests\DiDom;
4
+
5
+ use DiDom\Element;
6
+ use DiDom\ClassAttribute;
7
+ use Tests\TestCase;
8
+
9
+ class ClassAttributeTest extends TestCase
10
+ {
11
+ /**
12
+ * @expectedException \InvalidArgumentException
13
+ * @expectedExceptionMessage The element should contain DOMElement node
14
+ */
15
+ public function testConstructorWithTextNode()
16
+ {
17
+ $element = new Element(new \DOMText('foo'));
18
+
19
+ new ClassAttribute($element);
20
+ }
21
+
22
+ /**
23
+ * @expectedException \InvalidArgumentException
24
+ * @expectedExceptionMessage The element should contain DOMElement node
25
+ */
26
+ public function testConstructorWithCommentNode()
27
+ {
28
+ $element = new Element(new \DOMComment('foo'));
29
+
30
+ new ClassAttribute($element);
31
+ }
32
+
33
+ /**
34
+ * @expectedException \InvalidArgumentException
35
+ * @expectedExceptionMessage DiDom\ClassAttribute::add expects parameter 1 to be string, NULL given
36
+ */
37
+ public function testAddWithInvalidClassName()
38
+ {
39
+ $element = new Element('div', null, [
40
+ 'class' => 'foo',
41
+ ]);
42
+
43
+ $classAttribute = new ClassAttribute($element);
44
+
45
+ $classAttribute->add(null);
46
+ }
47
+
48
+ public function testAdd()
49
+ {
50
+ // without class attribute
51
+ $element = new Element('div', null);
52
+
53
+ $classAttribute = new ClassAttribute($element);
54
+
55
+ $this->assertEquals(null, $element->getAttribute('class'));
56
+
57
+ $classAttribute->add('foo');
58
+
59
+ $this->assertEquals('foo', $element->getAttribute('class'));
60
+
61
+ // with empty class attribute
62
+ $element = new Element('div', null, [
63
+ 'class' => '',
64
+ ]);
65
+
66
+ $classAttribute = new ClassAttribute($element);
67
+
68
+ $this->assertEquals('', $element->getAttribute('class'));
69
+
70
+ $classAttribute->add('foo');
71
+
72
+ $this->assertEquals('foo', $element->getAttribute('class'));
73
+
74
+ // class attribute with spaces
75
+ $element = new Element('div', null, [
76
+ 'class' => ' ',
77
+ ]);
78
+
79
+ $classAttribute = new ClassAttribute($element);
80
+
81
+ $this->assertEquals(' ', $element->getAttribute('class'));
82
+
83
+ $classAttribute->add('foo');
84
+
85
+ $this->assertEquals('foo', $element->getAttribute('class'));
86
+
87
+ // with not empty class attribute
88
+ $element = new Element('div', null, [
89
+ 'class' => 'foo bar',
90
+ ]);
91
+
92
+ $classAttribute = new ClassAttribute($element);
93
+
94
+ $this->assertEquals('foo bar', $element->getAttribute('class'));
95
+
96
+ $classAttribute->add('baz');
97
+
98
+ $this->assertEquals('foo bar baz', $element->getAttribute('class'));
99
+
100
+ // with existing class name
101
+ $element = new Element('div', null, [
102
+ 'class' => 'foo bar baz',
103
+ ]);
104
+
105
+ $classAttribute = new ClassAttribute($element);
106
+
107
+ $this->assertEquals('foo bar baz', $element->getAttribute('class'));
108
+
109
+ $classAttribute->add('bar');
110
+
111
+ $this->assertEquals('foo bar baz', $element->getAttribute('class'));
112
+ }
113
+
114
+ /**
115
+ * @expectedException \InvalidArgumentException
116
+ * @expectedExceptionMessage Class name must be a string, NULL given
117
+ */
118
+ public function testAddMultipleWithInvalidClassName()
119
+ {
120
+ $element = new Element('div', null, [
121
+ 'class' => 'foo',
122
+ ]);
123
+
124
+ $classAttribute = new ClassAttribute($element);
125
+
126
+ $classAttribute->addMultiple(['bar', null]);
127
+ }
128
+
129
+ public function testAddMultiple()
130
+ {
131
+ $element = new Element('div', null, [
132
+ 'class' => 'foo',
133
+ ]);
134
+
135
+ $classAttribute = new ClassAttribute($element);
136
+
137
+ $this->assertEquals('foo', $element->getAttribute('class'));
138
+
139
+ $classAttribute->addMultiple([
140
+ 'bar', 'baz',
141
+ ]);
142
+
143
+ $this->assertEquals('foo bar baz', $element->getAttribute('class'));
144
+ }
145
+
146
+ public function testGetAll()
147
+ {
148
+ // without class attribute
149
+ $element = new Element('div', null);
150
+
151
+ $classAttribute = new ClassAttribute($element);
152
+
153
+ $this->assertEquals([], $classAttribute->getAll());
154
+
155
+ // with empty class attribute
156
+ $element = new Element('div', null, [
157
+ 'class' => '',
158
+ ]);
159
+
160
+ $classAttribute = new ClassAttribute($element);
161
+
162
+ $this->assertEquals([], $classAttribute->getAll());
163
+
164
+ // class attribute with spaces
165
+ $element = new Element('div', null, [
166
+ 'class' => ' ',
167
+ ]);
168
+
169
+ $classAttribute = new ClassAttribute($element);
170
+
171
+ $this->assertEquals([], $classAttribute->getAll());
172
+
173
+ // one class
174
+ $element = new Element('div', null, [
175
+ 'class' => 'foo',
176
+ ]);
177
+
178
+ $classAttribute = new ClassAttribute($element);
179
+
180
+ $this->assertEquals(['foo'], $classAttribute->getAll());
181
+
182
+ // several classes
183
+ $element = new Element('div', null, [
184
+ 'class' => 'foo bar baz',
185
+ ]);
186
+
187
+ $classAttribute = new ClassAttribute($element);
188
+
189
+ $this->assertEquals(['foo', 'bar', 'baz'], $classAttribute->getAll());
190
+
191
+ // with multiple spaces between class names
192
+ $element = new Element('div', null, [
193
+ 'class' => 'foo bar baz',
194
+ ]);
195
+
196
+ $classAttribute = new ClassAttribute($element);
197
+
198
+ $this->assertEquals(['foo', 'bar', 'baz'], $classAttribute->getAll());
199
+ }
200
+
201
+ public function testGetAllPropertiesAfterEmptyClassAttribute()
202
+ {
203
+ $element = new Element('div', null, [
204
+ 'class' => 'foo bar baz',
205
+ ]);
206
+
207
+ $classAttribute = new ClassAttribute($element);
208
+
209
+ $this->assertEquals(['foo', 'bar', 'baz'], $classAttribute->getAll());
210
+
211
+ $element->setAttribute('class', '');
212
+
213
+ $this->assertEquals([], $classAttribute->getAll());
214
+ }
215
+
216
+ public function testContains()
217
+ {
218
+ $element = new Element('div', null, [
219
+ 'class' => 'foo bar',
220
+ ]);
221
+
222
+ $classAttribute = new ClassAttribute($element);
223
+
224
+ $this->assertTrue($classAttribute->contains('foo'));
225
+ $this->assertTrue($classAttribute->contains('bar'));
226
+ $this->assertFalse($classAttribute->contains('baz'));
227
+ }
228
+
229
+ /**
230
+ * @expectedException \InvalidArgumentException
231
+ * @expectedExceptionMessage DiDom\ClassAttribute::remove expects parameter 1 to be string, NULL given
232
+ */
233
+ public function testRemoveWithInvalidClassName()
234
+ {
235
+ $element = new Element('div', null, [
236
+ 'class' => 'foo bar baz',
237
+ ]);
238
+
239
+ $classAttribute = new ClassAttribute($element);
240
+
241
+ $classAttribute->remove(null);
242
+ }
243
+
244
+ public function testRemove()
245
+ {
246
+ $element = new Element('div', null, [
247
+ 'class' => 'foo bar baz',
248
+ ]);
249
+
250
+ $classAttribute = new ClassAttribute($element);
251
+
252
+ $this->assertEquals('foo bar baz', $element->getAttribute('class'));
253
+
254
+ $classAttribute->remove('bar');
255
+
256
+ $this->assertEquals('foo baz', $element->getAttribute('class'));
257
+
258
+ // with nonexistent class name
259
+ $element = new Element('div', null, [
260
+ 'class' => 'foo bar',
261
+ ]);
262
+
263
+ $classAttribute = new ClassAttribute($element);
264
+
265
+ $this->assertEquals('foo bar', $element->getAttribute('class'));
266
+
267
+ $classAttribute->remove('baz');
268
+
269
+ $this->assertEquals('foo bar', $element->getAttribute('class'));
270
+ }
271
+
272
+ /**
273
+ * @expectedException \InvalidArgumentException
274
+ * @expectedExceptionMessage Class name must be a string, NULL given
275
+ */
276
+ public function testRemoveMultipleWithInvalidClassName()
277
+ {
278
+ $element = new Element('div', null, [
279
+ 'class' => 'foo bar baz',
280
+ ]);
281
+
282
+ $classAttribute = new ClassAttribute($element);
283
+
284
+ $classAttribute->removeMultiple(['foo', null]);
285
+ }
286
+
287
+ public function testRemoveMultiple()
288
+ {
289
+ $element = new Element('div', null, [
290
+ 'class' => 'foo bar baz',
291
+ ]);
292
+
293
+ $classAttribute = new ClassAttribute($element);
294
+
295
+ $this->assertEquals('foo bar baz', $element->getAttribute('class'));
296
+
297
+ $classAttribute->removeMultiple(['foo', 'bar']);
298
+
299
+ $this->assertEquals('baz', $element->getAttribute('class'));
300
+
301
+ // with nonexistent class name
302
+ $element = new Element('div', null, [
303
+ 'class' => 'foo bar baz',
304
+ ]);
305
+
306
+ $classAttribute = new ClassAttribute($element);
307
+
308
+ $this->assertEquals('foo bar baz', $element->getAttribute('class'));
309
+
310
+ $classAttribute->removeMultiple(['bar', 'qux']);
311
+
312
+ $this->assertEquals('foo baz', $element->getAttribute('class'));
313
+ }
314
+
315
+ /**
316
+ * @expectedException \InvalidArgumentException
317
+ * @expectedExceptionMessage Class name must be a string, NULL given
318
+ */
319
+ public function testRemoveAllWithInvalidClassName()
320
+ {
321
+ $element = new Element('div', null, [
322
+ 'class' => 'foo bar baz',
323
+ ]);
324
+
325
+ $classAttribute = new ClassAttribute($element);
326
+
327
+ $classAttribute->removeAll(['foo', null]);
328
+ }
329
+
330
+ public function testRemoveAll()
331
+ {
332
+ $element = new Element('div', null, [
333
+ 'class' => 'foo bar baz',
334
+ ]);
335
+
336
+ $classAttribute = new ClassAttribute($element);
337
+
338
+ $this->assertEquals('foo bar baz', $element->getAttribute('class'));
339
+
340
+ $classAttribute->removeAll();
341
+
342
+ $this->assertEquals('', $element->getAttribute('class'));
343
+ }
344
+
345
+ public function testRemoveAllWithExclusions()
346
+ {
347
+ $element = new Element('div', null, [
348
+ 'class' => 'foo bar baz',
349
+ ]);
350
+
351
+ $classAttribute = new ClassAttribute($element);
352
+
353
+ $this->assertEquals('foo bar baz', $element->getAttribute('class'));
354
+
355
+ $classAttribute->removeAll(['bar']);
356
+
357
+ $this->assertEquals('bar', $element->getAttribute('class'));
358
+
359
+ // with nonexistent class name
360
+ $element = new Element('div', null, [
361
+ 'class' => 'foo bar baz',
362
+ ]);
363
+
364
+ $classAttribute = new ClassAttribute($element);
365
+
366
+ $this->assertEquals('foo bar baz', $element->getAttribute('class'));
367
+
368
+ $classAttribute->removeAll(['bar', 'qux']);
369
+
370
+ $this->assertEquals('bar', $element->getAttribute('class'));
371
+ }
372
+
373
+ public function testGetElement()
374
+ {
375
+ $element = new Element('div', null, [
376
+ 'class' => 'foo bar baz',
377
+ ]);
378
+
379
+ $classAttribute = new ClassAttribute($element);
380
+
381
+ $this->assertSame($element, $classAttribute->getElement());
382
+ }
383
+ }
vendor/imangazaliev/didom/tests/DiDom/DocumentTest.php ADDED
@@ -0,0 +1,709 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Tests\DiDom;
4
+
5
+ use DiDom\Document;
6
+ use DiDom\Query;
7
+ use Tests\TestCase;
8
+
9
+ class DocumentTest extends TestCase
10
+ {
11
+ /**
12
+ * @expectedException \InvalidArgumentException
13
+ * @expectedExceptionMessage DiDom\Document::load expects parameter 1 to be string, array given
14
+ */
15
+ public function testConstructWithInvalidArgument()
16
+ {
17
+ new Document(array('foo'));
18
+ }
19
+
20
+ /**
21
+ * @expectedException \InvalidArgumentException
22
+ * @expectedExceptionMessage DiDom\Document::__construct expects parameter 3 to be string, NULL given
23
+ */
24
+ public function testConstructWithInvalidEncoding()
25
+ {
26
+ new Document(null, false, null);
27
+ }
28
+
29
+ /**
30
+ * @expectedException \RuntimeException
31
+ * @expectedExceptionMessage Could not load file path/to/file
32
+ */
33
+ public function testConstructWithNotExistingFile()
34
+ {
35
+ new Document('path/to/file', true);
36
+ }
37
+
38
+ /**
39
+ * @expectedException \InvalidArgumentException
40
+ * @expectedExceptionMessage DiDom\Document::load expects parameter 3 to be string, NULL given
41
+ */
42
+ public function testConstructorWithInvalidTypeOfDocumentTypeArgument()
43
+ {
44
+ new Document('foo', false, 'UTF-8', null);
45
+ }
46
+
47
+ /**
48
+ * @expectedException \RuntimeException
49
+ * @expectedExceptionMessage Document type must be "xml" or "html", bar given
50
+ */
51
+ public function testConstructorWithInvalidDocumentType()
52
+ {
53
+ new Document('foo', false, 'UTF-8', 'bar');
54
+ }
55
+
56
+ /**
57
+ * @dataProvider loadHtmlCharsetTests
58
+ */
59
+ public function testLoadHtmlCharset($html, $text)
60
+ {
61
+ $document = new Document($html, false, 'UTF-8');
62
+
63
+ $this->assertEquals($text, $document->first('div')->text());
64
+ }
65
+
66
+ public function loadHtmlCharsetTests()
67
+ {
68
+ return array(
69
+ array('<html><div class="foo">English language</html>', 'English language'),
70
+ array('<html><div class="foo">Русский язык</html>', 'Русский язык'),
71
+ array('<html><div class="foo">اللغة العربية</html>', 'اللغة العربية'),
72
+ array('<html><div class="foo">漢語</html>', '漢語'),
73
+ array('<html><div class="foo">Tiếng Việt</html>', 'Tiếng Việt'),
74
+ );
75
+ }
76
+
77
+ public function testCreate()
78
+ {
79
+ $this->assertInstanceOf('DiDom\Document', Document::create());
80
+ }
81
+
82
+ public function testCreateElement()
83
+ {
84
+ $html = $this->loadFixture('posts.html');
85
+
86
+ $document = new Document($html, false);
87
+ $element = $document->createElement('span', 'value');
88
+
89
+ $this->assertInstanceOf('DiDom\Element', $element);
90
+ $this->assertEquals('span', $element->getNode()->tagName);
91
+ $this->assertEquals('value', $element->getNode()->textContent);
92
+
93
+ $element = $document->createElement('span');
94
+ $this->assertEquals('', $element->text());
95
+
96
+ $element = $document->createElement('input', '', ['name' => 'username']);
97
+ $this->assertEquals('username', $element->getNode()->getAttribute('name'));
98
+ }
99
+
100
+ public function testCreateElementBySelector()
101
+ {
102
+ $document = new Document();
103
+
104
+ $element = $document->createElementBySelector('a.external-link[href=http://example.com]');
105
+
106
+ $this->assertEquals('a', $element->tag);
107
+ $this->assertEquals('', $element->text());
108
+ $this->assertEquals(['href' => 'http://example.com', 'class' => 'external-link'], $element->attributes());
109
+
110
+ $element = $document->createElementBySelector('#block', 'Foo');
111
+
112
+ $this->assertEquals('div', $element->tag);
113
+ $this->assertEquals('Foo', $element->text());
114
+ $this->assertEquals(['id' => 'block'], $element->attributes());
115
+
116
+ $element = $document->createElementBySelector('input', null, ['name' => 'name', 'placeholder' => 'Enter your name']);
117
+
118
+ $this->assertEquals('input', $element->tag);
119
+ $this->assertEquals('', $element->text());
120
+ $this->assertEquals(['name' => 'name', 'placeholder' => 'Enter your name'], $element->attributes());
121
+ }
122
+
123
+ /**
124
+ * @expectedException \InvalidArgumentException
125
+ * @expectedExceptionMessage Argument 1 passed to DiDom\Document::appendChild must be an instance of DiDom\Element or DOMNode, string given
126
+ */
127
+ public function testAppendChildWithInvalidArgument()
128
+ {
129
+ $html = $this->loadFixture('posts.html');
130
+
131
+ $document = new Document($html);
132
+
133
+ $document->appendChild('foo');
134
+ }
135
+
136
+ public function testAppendChild()
137
+ {
138
+ $html = '<!DOCTYPE html>
139
+ <html lang="en">
140
+ <head>
141
+ <meta charset="UTF-8">
142
+ <title>Document</title>
143
+ </head>
144
+ <body>
145
+
146
+ </body>
147
+ </html>';
148
+
149
+ $document = new Document($html);
150
+
151
+ $this->assertCount(0, $document->find('span'));
152
+
153
+ $node = $document->createElement('span');
154
+ $appendedChild = $document->appendChild($node);
155
+
156
+ $this->assertCount(1, $document->find('span'));
157
+ $this->assertTrue($appendedChild->is($document->first('span')));
158
+
159
+ $appendedChild->remove();
160
+
161
+ $this->assertCount(0, $document->find('span'));
162
+
163
+ $nodes = [];
164
+ $nodes[] = $document->createElement('span');
165
+ $nodes[] = $document->createElement('span');
166
+
167
+ $appendedChildren = $document->appendChild($nodes);
168
+
169
+ $nodes = $document->find('span');
170
+
171
+ $this->assertCount(2, $appendedChildren);
172
+ $this->assertCount(2, $nodes);
173
+
174
+ foreach ($appendedChildren as $index => $child) {
175
+ $this->assertTrue($child->is($nodes[$index]));
176
+ }
177
+ }
178
+
179
+ /**
180
+ * @expectedException \InvalidArgumentException
181
+ * @expectedExceptionMessage DiDom\Document::load expects parameter 1 to be string, NULL given
182
+ */
183
+ public function testLoadWithInvalidContentArgument()
184
+ {
185
+ $document = new Document();
186
+
187
+ $document->load(null);
188
+ }
189
+
190
+ /**
191
+ * @expectedException \RuntimeException
192
+ * @expectedExceptionMessage Could not load file path/to/file
193
+ */
194
+ public function testLoadWithNotExistingFile()
195
+ {
196
+ $document = new Document();
197
+
198
+ $document->load('path/to/file', true);
199
+ }
200
+
201
+ /**
202
+ * @expectedException \InvalidArgumentException
203
+ * @expectedExceptionMessage DiDom\Document::load expects parameter 3 to be string, NULL given
204
+ */
205
+ public function testLoadWithInvalidTypeOfDocumentTypeArgument()
206
+ {
207
+ $document = new Document();
208
+
209
+ $document->load('foo', false, null);
210
+ }
211
+
212
+ /**
213
+ * @expectedException \RuntimeException
214
+ * @expectedExceptionMessage Document type must be "xml" or "html", bar given
215
+ */
216
+ public function testLoadWithInvalidDocumentType()
217
+ {
218
+ $document = new Document();
219
+
220
+ $document->load('foo', false, 'bar');
221
+ }
222
+
223
+ /**
224
+ * @expectedException \InvalidArgumentException
225
+ * @expectedExceptionMessage DiDom\Document::load expects parameter 4 to be integer, string given
226
+ */
227
+ public function testLoadWithInvalidOptionsType()
228
+ {
229
+ $document = new Document();
230
+
231
+ $document->load('foo', false, 'html', 'bar');
232
+ }
233
+
234
+ public function testLoadHtmlDocument()
235
+ {
236
+ $html = '
237
+ <!DOCTYPE html>
238
+ <html>
239
+ <head>
240
+ <title>Document</title>
241
+ </head>
242
+ <body>
243
+ <div class="foo">Foo — Bar — Baz</div>
244
+ </body>
245
+ </html>
246
+ ';
247
+
248
+ $document = new Document();
249
+
250
+ $document->load($html, false, 'html');
251
+
252
+ $this->assertEquals('Foo — Bar — Baz', $document->first('.foo')->text());
253
+ }
254
+
255
+ public function testLoadXmlDocument()
256
+ {
257
+ $xml = '
258
+ <?xml version="1.0" encoding="UTF-8"?>
259
+ <root>
260
+ <foo>Foo — Bar — Baz</foo>
261
+ </root>
262
+ ';
263
+
264
+ $document = new Document();
265
+
266
+ $document->load($xml, false, 'xml');
267
+
268
+ $this->assertEquals('Foo — Bar — Baz', $document->first('foo')->text());
269
+ }
270
+
271
+ /**
272
+ * @expectedException \InvalidArgumentException
273
+ * @expectedExceptionMessage DiDom\Document::load expects parameter 1 to be string, NULL given
274
+ */
275
+ public function testLoadHtmlWithInvalidArgument()
276
+ {
277
+ $document = new Document();
278
+
279
+ $document->loadHtml(null);
280
+ }
281
+
282
+ /**
283
+ * @expectedException \InvalidArgumentException
284
+ * @expectedExceptionMessage DiDom\Document::load expects parameter 1 to be string, array given
285
+ */
286
+ public function testLoadHtmlFileWithInvalidArgument()
287
+ {
288
+ $document = new Document();
289
+
290
+ $document->loadHtmlFile(array('foo'));
291
+ }
292
+
293
+ /**
294
+ * @expectedException \RuntimeException
295
+ * @expectedExceptionMessage Could not load file path/to/file
296
+ */
297
+ public function testLoadHtmlFileWithNotExistingFile()
298
+ {
299
+ $document = new Document();
300
+
301
+ $document->loadHtmlFile('path/to/file');
302
+ }
303
+
304
+ /**
305
+ * @expectedException \InvalidArgumentException
306
+ * @expectedExceptionMessage DiDom\Document::load expects parameter 1 to be string, NULL given
307
+ */
308
+ public function testLoadXmlWithInvalidArgument()
309
+ {
310
+ $document = new Document();
311
+
312
+ $document->loadXml(null);
313
+ }
314
+
315
+ /**
316
+ * @expectedException \InvalidArgumentException
317
+ * @expectedExceptionMessage DiDom\Document::load expects parameter 1 to be string, array given
318
+ */
319
+ public function testLoadXmlFileWithInvalidArgument()
320
+ {
321
+ $document = new Document();
322
+
323
+ $document->loadXmlFile(array('foo'));
324
+ }
325
+
326
+ /**
327
+ * @expectedException \RuntimeException
328
+ * @expectedExceptionMessage Could not load file path/to/file
329
+ */
330
+ public function testLoadXmlFileWithNotExistingFile()
331
+ {
332
+ $document = new Document();
333
+
334
+ $document->loadXmlFile('path/to/file');
335
+ }
336
+
337
+ public function testHas()
338
+ {
339
+ $document = new Document($this->loadFixture('posts.html'));
340
+
341
+ $this->assertTrue($document->has('.posts'));
342
+ $this->assertFalse($document->has('.fake'));
343
+ }
344
+
345
+ /**
346
+ * @dataProvider findTests
347
+ */
348
+ public function testFind($html, $selector, $type, $count)
349
+ {
350
+ $document = new Document($html);
351
+ $elements = $document->find($selector, $type);
352
+
353
+ $this->assertTrue(is_array($elements));
354
+ $this->assertEquals($count, count($elements));
355
+
356
+ foreach ($elements as $element) {
357
+ $this->assertInstanceOf('DiDom\Element', $element);
358
+ }
359
+ }
360
+
361
+ /**
362
+ * @dataProvider findTests
363
+ */
364
+ public function testFindAndReturnDomElement($html, $selector, $type, $count)
365
+ {
366
+ $document = new Document($html);
367
+ $elements = $document->find($selector, $type, false);
368
+
369
+ $this->assertTrue(is_array($elements));
370
+ $this->assertEquals($count, count($elements));
371
+
372
+ foreach ($elements as $element) {
373
+ $this->assertInstanceOf('DOMElement', $element);
374
+ }
375
+ }
376
+
377
+ public function testFindWithContext()
378
+ {
379
+ $document = new Document($this->loadFixture('posts.html'));
380
+
381
+ $post = $document->find('.post')[1];
382
+ $title = $document->find('.post .title')[1];
383
+
384
+ $titleInContext = $document->find('.title', Query::TYPE_CSS, true, $post)[0];
385
+
386
+ $this->assertTrue($title->is($titleInContext));
387
+ $this->assertFalse($title->is($post->find('.title')[0]));
388
+ }
389
+
390
+ public function testFindText()
391
+ {
392
+ $html = $this->loadFixture('menu.html');
393
+
394
+ $document = new Document($html);
395
+ $texts = $document->find('//a/text()', Query::TYPE_XPATH);
396
+
397
+ $this->assertTrue(is_array($texts));
398
+ $this->assertEquals(3, count($texts));
399
+
400
+ $this->assertEquals(['Link 1', 'Link 2', 'Link 3'], $texts);
401
+ }
402
+
403
+ public function testFindAttribute()
404
+ {
405
+ $html = $this->loadFixture('menu.html');
406
+
407
+ $document = new Document($html);
408
+ $links = $document->find('//a/@href', Query::TYPE_XPATH);
409
+
410
+ $this->assertTrue(is_array($links));
411
+ $this->assertEquals(3, count($links));
412
+
413
+ foreach ($links as $link) {
414
+ $this->assertEquals('http://example.com', $link);
415
+ }
416
+ }
417
+
418
+ public function findTests()
419
+ {
420
+ $html = $this->loadFixture('posts.html');
421
+
422
+ return array(
423
+ array($html, '.post h2', Query::TYPE_CSS, 3),
424
+ array($html, '.fake h2', Query::TYPE_CSS, 0),
425
+ array($html, '.post h2, .post p', Query::TYPE_CSS, 6),
426
+ array($html, "//*[contains(concat(' ', normalize-space(@class), ' '), ' post ')]", Query::TYPE_XPATH, 3),
427
+ );
428
+ }
429
+
430
+ public function testFirst()
431
+ {
432
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
433
+
434
+ $document = new Document($html, false);
435
+
436
+ $items = $document->find('ul > li');
437
+
438
+ $this->assertEquals($items[0]->getNode(), $document->first('ul > li')->getNode());
439
+
440
+ $this->assertEquals('One', $document->first('ul > li::text'));
441
+
442
+ $document = new Document();
443
+
444
+ $this->assertNull($document->first('ul > li'));
445
+ }
446
+
447
+ public function testFirstWithContext()
448
+ {
449
+ $html = '
450
+ <div class="root">
451
+ <span>Foo</span>
452
+
453
+ <div><span>Bar</span></div>
454
+ </div>
455
+ ';
456
+
457
+ $document = new Document($html);
458
+
459
+ $div = $document->first('.root div');
460
+ $span = $document->first('.root div span');
461
+
462
+ $result = $document->first('span', Query::TYPE_CSS, true, $div);
463
+
464
+ $this->assertTrue($span->is($result));
465
+ }
466
+
467
+ public function testXpath()
468
+ {
469
+ $html = $this->loadFixture('posts.html');
470
+
471
+ $document = new Document($html, false);
472
+ $elements = $document->xpath("//*[contains(concat(' ', normalize-space(@class), ' '), ' post ')]");
473
+
474
+ $this->assertTrue(is_array($elements));
475
+ $this->assertEquals(3, count($elements));
476
+
477
+ foreach ($elements as $element) {
478
+ $this->assertInstanceOf('DiDom\Element', $element);
479
+ }
480
+ }
481
+
482
+ public function testCount()
483
+ {
484
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
485
+
486
+ $document = new Document($html, false);
487
+
488
+ $this->assertInternalType('int', $document->count('li'));
489
+ $this->assertEquals(3, $document->count('li'));
490
+
491
+ $document = new Document();
492
+
493
+ $this->assertInternalType('int', $document->count('li'));
494
+ $this->assertEquals(0, $document->count('li'));
495
+ }
496
+
497
+ public function testHtml()
498
+ {
499
+ $html = '
500
+ <!DOCTYPE html>
501
+ <html lang="en">
502
+ <head>
503
+ <meta charset="UTF-8">
504
+ <title>Document</title>
505
+ </head>
506
+ <body>
507
+ English language <br>
508
+ Русский язык <br>
509
+ اللغة العربية <br>
510
+ 漢語 <br>
511
+ Tiếng Việt <br>
512
+
513
+ &lt; &gt;
514
+ </body>
515
+ </html>
516
+ ';
517
+
518
+ $document = new Document($html);
519
+
520
+ $this->assertEquals(trim($html), $document->html());
521
+ }
522
+
523
+ public function testXml()
524
+ {
525
+ $xml = $this->loadFixture('books.xml');
526
+ $document = new Document($xml, false, 'UTF-8', 'xml');
527
+
528
+ $this->assertTrue(is_string($document->xml()));
529
+ }
530
+
531
+ public function testXmlWithOptions()
532
+ {
533
+ $xml = '<foo><bar></bar></foo>';
534
+
535
+ $document = new Document();
536
+ $document->loadXml($xml);
537
+
538
+ $prolog = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
539
+
540
+ $this->assertEquals($prolog.'<foo><bar/></foo>', $document->xml());
541
+ $this->assertEquals($prolog.'<foo><bar></bar></foo>', $document->xml(LIBXML_NOEMPTYTAG));
542
+ }
543
+
544
+ public function testFormat()
545
+ {
546
+ $html = $this->loadFixture('posts.html');
547
+ $document = new Document($html, false);
548
+
549
+ $this->assertFalse($document->getDocument()->formatOutput);
550
+
551
+ $document->format();
552
+
553
+ $this->assertTrue($document->getDocument()->formatOutput);
554
+ }
555
+
556
+ public function testText()
557
+ {
558
+ $html = '<html>foo</html>';
559
+ $document = new Document($html, false);
560
+
561
+ $this->assertEquals('foo', $document->text());
562
+ }
563
+
564
+ /**
565
+ * @expectedException InvalidArgumentException
566
+ */
567
+ public function testIsWithInvalidArgument()
568
+ {
569
+ $document = new Document();
570
+ $document->is(null);
571
+ }
572
+
573
+ public function testIs()
574
+ {
575
+ $html = $this->loadFixture('posts.html');
576
+
577
+ $document = new Document($html, false);
578
+ $document2 = new Document($html, false);
579
+
580
+ $this->assertTrue($document->is($document));
581
+ $this->assertFalse($document->is($document2));
582
+ }
583
+
584
+ public function testIsWithEmptyDocument()
585
+ {
586
+ $html = $this->loadFixture('posts.html');
587
+
588
+ $document = new Document($html, false);
589
+ $document2 = new Document();
590
+
591
+ $this->assertFalse($document->is($document2));
592
+ }
593
+
594
+ public function testGetType()
595
+ {
596
+ // empty document
597
+
598
+ $document = new Document();
599
+
600
+ $this->assertNull($document->getType());
601
+
602
+ // html
603
+
604
+ $html = $this->loadFixture('posts.html');
605
+
606
+ $document = new Document($html);
607
+ $this->assertEquals('html', $document->getType());
608
+
609
+ $document = new Document();
610
+ $document->loadHtml($html);
611
+ $this->assertEquals('html', $document->getType());
612
+
613
+ $document = new Document();
614
+ $document->load($html, false, 'html');
615
+ $this->assertEquals('html', $document->getType());
616
+
617
+ // xml
618
+
619
+ $xml = $this->loadFixture('books.xml');
620
+
621
+ $document = new Document($xml, false, 'UTF-8', 'xml');
622
+ $this->assertEquals('xml', $document->getType());
623
+
624
+ $document = new Document();
625
+ $document->loadXml($xml);
626
+ $this->assertEquals('xml', $document->getType());
627
+
628
+ $document = new Document();
629
+ $document->load($xml, false, 'xml');
630
+ $this->assertEquals('xml', $document->getType());
631
+ }
632
+
633
+ public function testGetEncoding()
634
+ {
635
+ $document = new Document();
636
+
637
+ $this->assertEquals('UTF-8', $document->getEncoding());
638
+
639
+ $document = new Document(null, false, 'CP-1251');
640
+
641
+ $this->assertEquals('CP-1251', $document->getEncoding());
642
+ }
643
+
644
+ public function testGetDocument()
645
+ {
646
+ $domDocument = new \DOMDocument();
647
+ $document = new Document($domDocument);
648
+
649
+ $this->assertEquals($domDocument, $document->getDocument());
650
+ }
651
+
652
+ public function testGetElement()
653
+ {
654
+ $html = $this->loadFixture('posts.html');
655
+ $document = new Document($html, false);
656
+
657
+ $this->assertInstanceOf('DOMElement', $document->getElement());
658
+ }
659
+
660
+ /**
661
+ * @expectedException RuntimeException
662
+ */
663
+ public function testEmptyDocumentToElement()
664
+ {
665
+ $document = new Document();
666
+
667
+ $document->toElement();
668
+ }
669
+
670
+ public function testToElement()
671
+ {
672
+ $html = $this->loadFixture('posts.html');
673
+ $document = new Document($html, false);
674
+
675
+ $this->assertInstanceOf('DiDom\Element', $document->toElement());
676
+ }
677
+
678
+ public function testToStringHtml()
679
+ {
680
+ $html = $this->loadFixture('posts.html');
681
+ $document = new Document($html, false);
682
+
683
+ $this->assertEquals($document->html(), $document->__toString());
684
+ }
685
+
686
+ public function testToStringXml()
687
+ {
688
+ $xml = $this->loadFixture('books.xml');
689
+ $document = new Document($xml, false, 'UTF-8', 'xml');
690
+
691
+ $this->assertEquals($document->xml(), $document->__toString());
692
+ }
693
+
694
+ /**
695
+ * @dataProvider findTests
696
+ */
697
+ public function testInvoke($html, $selector, $type, $count)
698
+ {
699
+ $document = new Document($html, false);
700
+ $elements = $document($selector, $type);
701
+
702
+ $this->assertTrue(is_array($elements));
703
+ $this->assertEquals($count, count($elements));
704
+
705
+ foreach ($elements as $element) {
706
+ $this->assertInstanceOf('DiDom\Element', $element);
707
+ }
708
+ }
709
+ }
vendor/imangazaliev/didom/tests/DiDom/ElementTest.php ADDED
@@ -0,0 +1,2056 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Tests\DiDom;
4
+
5
+ use DiDom\Document;
6
+ use DiDom\Element;
7
+ use DiDom\Query;
8
+ use Tests\TestCase;
9
+
10
+ class ElementTest extends TestCase
11
+ {
12
+ /**
13
+ * @expectedException \InvalidArgumentException
14
+ */
15
+ public function testConstructorWithInvalidName()
16
+ {
17
+ new Element(null);
18
+ }
19
+
20
+ /**
21
+ * @expectedException \InvalidArgumentException
22
+ */
23
+ public function testConstructorWithInvalidValue()
24
+ {
25
+ new Element('span', []);
26
+ }
27
+
28
+ public function testConstructorWithInvalidAttributes()
29
+ {
30
+ if (PHP_VERSION_ID >= 70000) {
31
+ $this->setExpectedException('TypeError');
32
+ } else {
33
+ $this->setExpectedException('PHPUnit_Framework_Error');
34
+ }
35
+
36
+ new Element('span', 'Foo', null);
37
+ }
38
+
39
+ public function testConstructor()
40
+ {
41
+ $element = new Element('input', null, ['name' => 'username', 'value' => 'John']);
42
+
43
+ $this->assertEquals('input', $element->getNode()->tagName);
44
+ $this->assertEquals('username', $element->getNode()->getAttribute('name'));
45
+ $this->assertEquals('John', $element->getNode()->getAttribute('value'));
46
+
47
+ // create from DOMElement
48
+ $node = new \DOMElement('span', 'Foo');
49
+ $element = new Element($node);
50
+
51
+ $this->assertEquals($node, $element->getNode());
52
+
53
+ // create from DOMText
54
+ $node = new \DOMText('Foo');
55
+ $element = new Element($node);
56
+
57
+ $this->assertEquals($node, $element->getNode());
58
+
59
+ // create from DOMComment
60
+ $node = new \DOMComment('Foo');
61
+ $element = new Element($node);
62
+
63
+ $this->assertEquals($node, $element->getNode());
64
+ }
65
+
66
+ public function testCreate()
67
+ {
68
+ $element = Element::create('span', 'Foo', ['class' => 'bar']);
69
+
70
+ $this->assertEquals('span', $element->tag);
71
+ $this->assertEquals('Foo', $element->text());
72
+ $this->assertEquals(['class' => 'bar'], $element->attributes());
73
+ }
74
+
75
+ public function testCreateBySelector()
76
+ {
77
+ $element = Element::createBySelector('li.item.active', 'Foo', ['data-id' => 1]);
78
+
79
+ $this->assertEquals('li', $element->tag);
80
+ $this->assertEquals('Foo', $element->text());
81
+ $this->assertEquals(['class' => 'item active', 'data-id' => 1], $element->attributes());
82
+ }
83
+
84
+ /**
85
+ * @expectedException \InvalidArgumentException
86
+ */
87
+ public function testPrependChildWithInvalidArgument()
88
+ {
89
+ $element = new Element('span', 'hello');
90
+
91
+ $element->prependChild('foo');
92
+ }
93
+
94
+ /**
95
+ * @expectedException \LogicException
96
+ * @expectedExceptionMessage Can not prepend child to element without owner document
97
+ */
98
+ public function testPrependChildWithoutParentNode()
99
+ {
100
+ $element = new Element(new \DOMElement('div'));
101
+
102
+ $element->prependChild(new Element('div'));
103
+ }
104
+
105
+ public function testPrependChild()
106
+ {
107
+ $list = new Element('ul');
108
+
109
+ $this->assertEquals(0, $list->getNode()->childNodes->length);
110
+
111
+ $item = new Element('li', 'bar');
112
+
113
+ $prependedChild = $list->prependChild($item);
114
+
115
+ $this->assertEquals(1, $list->getNode()->childNodes->length);
116
+ $this->assertInstanceOf('DiDom\Element', $prependedChild);
117
+ $this->assertEquals('bar', $prependedChild->getNode()->textContent);
118
+
119
+ $item = new Element('li', 'foo');
120
+
121
+ $prependedChild = $list->prependChild($item);
122
+
123
+ $this->assertEquals(2, $list->getNode()->childNodes->length);
124
+ $this->assertInstanceOf('DiDom\Element', $prependedChild);
125
+ $this->assertEquals('foo', $prependedChild->getNode()->textContent);
126
+
127
+ $this->assertEquals('foo', $list->getNode()->childNodes->item(0)->textContent);
128
+ $this->assertEquals('bar', $list->getNode()->childNodes->item(1)->textContent);
129
+ }
130
+
131
+ public function testPrependChildWithArrayOfNodes()
132
+ {
133
+ $list = new Element('ul');
134
+
135
+ $prependedChild = $list->prependChild(new Element('li', 'foo'));
136
+
137
+ $this->assertEquals(1, $list->getNode()->childNodes->length);
138
+ $this->assertInstanceOf('DiDom\Element', $prependedChild);
139
+ $this->assertEquals('foo', $prependedChild->getNode()->textContent);
140
+
141
+ $items = [];
142
+
143
+ $items[] = new Element('li', 'bar');
144
+ $items[] = new Element('li', 'baz');
145
+
146
+ $appendedChildren = $list->prependChild($items);
147
+
148
+ $this->assertCount(2, $appendedChildren);
149
+ $this->assertEquals(3, $list->getNode()->childNodes->length);
150
+
151
+ foreach ($appendedChildren as $appendedChild) {
152
+ $this->assertInstanceOf('DiDom\Element', $appendedChild);
153
+ }
154
+
155
+ foreach (['bar', 'baz', 'foo'] as $index => $value) {
156
+ $this->assertEquals($value, $list->getNode()->childNodes->item($index)->textContent);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * @expectedException \InvalidArgumentException
162
+ */
163
+ public function testAppendChildWithInvalidArgument()
164
+ {
165
+ $element = new Element('span', 'hello');
166
+
167
+ $element->appendChild('foo');
168
+ }
169
+
170
+ /**
171
+ * @expectedException \LogicException
172
+ * @expectedExceptionMessage Can not append child to element without owner document
173
+ */
174
+ public function testAppendChildWithoutParentNode()
175
+ {
176
+ $element = new Element(new \DOMElement('div'));
177
+
178
+ $element->appendChild(new Element('div'));
179
+ }
180
+
181
+ public function testAppendChild()
182
+ {
183
+ $list = new Element('ul');
184
+
185
+ $this->assertEquals(0, $list->getNode()->childNodes->length);
186
+
187
+ $item = new Element('li', 'foo');
188
+ $appendedChild = $list->appendChild($item);
189
+
190
+ $this->assertEquals(1, $list->getNode()->childNodes->length);
191
+ $this->assertInstanceOf('DiDom\Element', $appendedChild);
192
+ $this->assertEquals('foo', $appendedChild->getNode()->textContent);
193
+
194
+ $item = new Element('li', 'bar');
195
+ $appendedChild = $list->appendChild($item);
196
+
197
+ $this->assertEquals(2, $list->getNode()->childNodes->length);
198
+ $this->assertInstanceOf('DiDom\Element', $appendedChild);
199
+ $this->assertEquals('bar', $appendedChild->getNode()->textContent);
200
+
201
+ $this->assertEquals('foo', $list->getNode()->childNodes->item(0)->textContent);
202
+ $this->assertEquals('bar', $list->getNode()->childNodes->item(1)->textContent);
203
+ }
204
+
205
+ public function testAppendChildWithArray()
206
+ {
207
+ $list = new Element('ul');
208
+
209
+ $appendedChild = $list->appendChild(new Element('li', 'foo'));
210
+
211
+ $this->assertEquals(1, $list->getNode()->childNodes->length);
212
+ $this->assertInstanceOf('DiDom\Element', $appendedChild);
213
+ $this->assertEquals('foo', $appendedChild->getNode()->textContent);
214
+
215
+ $items = [];
216
+
217
+ $items[] = new Element('li', 'bar');
218
+ $items[] = new Element('li', 'baz');
219
+
220
+ $appendedChildren = $list->appendChild($items);
221
+
222
+ $this->assertCount(2, $appendedChildren);
223
+ $this->assertEquals(3, $list->getNode()->childNodes->length);
224
+
225
+ foreach ($appendedChildren as $appendedChild) {
226
+ $this->assertInstanceOf('DiDom\Element', $appendedChild);
227
+ }
228
+
229
+ foreach (['foo', 'bar', 'baz'] as $index => $value) {
230
+ $this->assertEquals($value, $list->getNode()->childNodes->item($index)->textContent);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * @expectedException \InvalidArgumentException
236
+ */
237
+ public function testInsertBeforeWithInvalidNodeArgument()
238
+ {
239
+ $list = new Element('ul');
240
+
241
+ $list->insertBefore('foo');
242
+ }
243
+
244
+ /**
245
+ * @expectedException \InvalidArgumentException
246
+ * @expectedExceptionMessage Argument 2 passed to DiDom\Element::insertBefore must be an instance of DiDom\Element or DOMNode, string given
247
+ */
248
+ public function testInsertBeforeWithInvalidReferenceNodeArgument()
249
+ {
250
+ $list = new Element('ul');
251
+
252
+ $list->insertBefore(new Element('li', 'foo'), 'foo');
253
+ }
254
+
255
+ /**
256
+ * @expectedException \LogicException
257
+ * @expectedExceptionMessage Can not insert child to element without owner document
258
+ */
259
+ public function testInsertBeforeWithoutParentNode()
260
+ {
261
+ $list = new Element(new \DOMElement('ul'));
262
+
263
+ $list->insertBefore(new Element('li', 'foo'));
264
+ }
265
+
266
+ public function testInsertBefore()
267
+ {
268
+ $list = new Element('ul');
269
+
270
+ $insertedNode = $list->insertBefore(new Element('li', 'qux'));
271
+
272
+ $this->assertInstanceOf('DiDom\Element', $insertedNode);
273
+ $this->assertEquals('qux', $insertedNode->getNode()->textContent);
274
+
275
+ foreach (['qux'] as $index => $value) {
276
+ $this->assertEquals($value, $list->getNode()->childNodes->item($index)->textContent);
277
+ }
278
+
279
+ $list->insertBefore(new Element('li', 'foo'), $list->getNode()->childNodes->item(0));
280
+
281
+ foreach (['foo', 'qux'] as $index => $value) {
282
+ $this->assertEquals($value, $list->getNode()->childNodes->item($index)->textContent);
283
+ }
284
+
285
+ $list->insertBefore(new Element('li', 'baz'), $list->getNode()->childNodes->item(1));
286
+
287
+ foreach (['foo', 'baz', 'qux'] as $index => $value) {
288
+ $this->assertEquals($value, $list->getNode()->childNodes->item($index)->textContent);
289
+ }
290
+
291
+ $list->insertBefore(new Element('li', 'bar'), $list->getNode()->childNodes->item(1));
292
+
293
+ foreach (['foo', 'bar', 'baz', 'qux'] as $index => $value) {
294
+ $this->assertEquals($value, $list->getNode()->childNodes->item($index)->textContent);
295
+ }
296
+ }
297
+
298
+ public function testInsertBeforeWithoutReferenceNode()
299
+ {
300
+ $list = new Element('ul');
301
+
302
+ $list->insertBefore(new Element('li', 'foo'));
303
+ $list->insertBefore(new Element('li', 'bar'));
304
+
305
+ foreach (['foo', 'bar'] as $index => $value) {
306
+ $this->assertEquals($value, $list->getNode()->childNodes->item($index)->textContent);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * @expectedException \InvalidArgumentException
312
+ */
313
+ public function testInsertAfterWithInvalidNodeArgument()
314
+ {
315
+ $list = new Element('ul');
316
+
317
+ $list->insertAfter('foo');
318
+ }
319
+
320
+ /**
321
+ * @expectedException \InvalidArgumentException
322
+ * @expectedExceptionMessage Argument 2 passed to DiDom\Element::insertAfter must be an instance of DiDom\Element or DOMNode, string given
323
+ */
324
+ public function testInsertAfterWithInvalidReferenceNodeArgument()
325
+ {
326
+ $list = new Element('ul');
327
+
328
+ $list->insertAfter(new Element('li', 'foo'), 'foo');
329
+ }
330
+
331
+ /**
332
+ * @expectedException \LogicException
333
+ * @expectedExceptionMessage Can not insert child to element without owner document
334
+ */
335
+ public function testInsertAfterWithoutParentNode()
336
+ {
337
+ $list = new Element(new \DOMElement('ul'));
338
+
339
+ $list->insertAfter(new Element('li', 'foo'));
340
+ }
341
+
342
+ public function testInsertAfter()
343
+ {
344
+ $list = new Element('ul');
345
+
346
+ $insertedNode = $list->insertAfter(new Element('li', 'foo'));
347
+
348
+ $this->assertInstanceOf('DiDom\Element', $insertedNode);
349
+ $this->assertEquals('foo', $insertedNode->getNode()->textContent);
350
+
351
+ foreach (['foo'] as $index => $value) {
352
+ $this->assertEquals($value, $list->getNode()->childNodes->item($index)->textContent);
353
+ }
354
+
355
+ $list->insertAfter(new Element('li', 'qux'), $list->getNode()->childNodes->item(0));
356
+
357
+ foreach (['foo', 'qux'] as $index => $value) {
358
+ $this->assertEquals($value, $list->getNode()->childNodes->item($index)->textContent);
359
+ }
360
+
361
+ $list->insertAfter(new Element('li', 'bar'), $list->getNode()->childNodes->item(0));
362
+
363
+ foreach (['foo', 'bar', 'qux'] as $index => $value) {
364
+ $this->assertEquals($value, $list->getNode()->childNodes->item($index)->textContent);
365
+ }
366
+
367
+ $list->insertAfter(new Element('li', 'baz'), $list->getNode()->childNodes->item(1));
368
+
369
+ foreach (['foo', 'bar', 'baz', 'qux'] as $index => $value) {
370
+ $this->assertEquals($value, $list->getNode()->childNodes->item($index)->textContent);
371
+ }
372
+ }
373
+
374
+ public function testInsertAfterWithoutReferenceNode()
375
+ {
376
+ $list = new Element('ul');
377
+
378
+ $list->insertAfter(new Element('li', 'foo'));
379
+ $list->insertAfter(new Element('li', 'bar'));
380
+
381
+ foreach (['foo', 'bar'] as $index => $value) {
382
+ $this->assertEquals($value, $list->getNode()->childNodes->item($index)->textContent);
383
+ }
384
+ }
385
+
386
+ public function testHas()
387
+ {
388
+ $document = new \DOMDocument();
389
+ $document->loadHTML('<div><span class="foo">bar</span></div>');
390
+
391
+ $node = $document->getElementsByTagName('div')->item(0);
392
+ $element = new Element($node);
393
+
394
+ $this->assertTrue($element->has('.foo'));
395
+ $this->assertFalse($element->has('.bar'));
396
+ }
397
+
398
+ /**
399
+ * @dataProvider findTests
400
+ */
401
+ public function testFind($html, $selector, $type, $count)
402
+ {
403
+ $document = new \DOMDocument();
404
+ $document->loadHTML($html);
405
+
406
+ $domElement = $document->getElementsByTagName('body')->item(0);
407
+ $element = new Element($domElement);
408
+
409
+ $elements = $element->find($selector, $type);
410
+
411
+ $this->assertTrue(is_array($elements));
412
+ $this->assertEquals($count, count($elements));
413
+
414
+ foreach ($elements as $element) {
415
+ $this->assertInstanceOf('DiDom\Element', $element);
416
+ }
417
+ }
418
+
419
+ /**
420
+ * @expectedException \LogicException
421
+ */
422
+ public function testFindInDocumentWithoutOwnerDocument()
423
+ {
424
+ $element = new Element(new \DOMElement('div'));
425
+
426
+ $element->findInDocument('.foo');
427
+ }
428
+
429
+ public function testFindInDocument()
430
+ {
431
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
432
+
433
+ $document = new Document($html);
434
+
435
+ $items = $document->find('li');
436
+ $list = $document->first('ul');
437
+
438
+ foreach ($list->find('li') as $index => $item) {
439
+ $this->assertFalse($item->is($items[$index]));
440
+ }
441
+
442
+ foreach ($list->findInDocument('li') as $index => $item) {
443
+ $this->assertTrue($item->is($items[$index]));
444
+ }
445
+
446
+ $this->assertCount(3, $document->find('li'));
447
+
448
+ $list->findInDocument('li')[0]->remove();
449
+
450
+ $this->assertCount(2, $document->find('li'));
451
+ }
452
+
453
+ /**
454
+ * @expectedException \LogicException
455
+ */
456
+ public function testFirstInDocumentWithoutOwnerDocument()
457
+ {
458
+ $element = new Element(new \DOMElement('div'));
459
+
460
+ $element->firstInDocument('.foo');
461
+ }
462
+
463
+ public function testFirstInDocument()
464
+ {
465
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
466
+
467
+ $document = new Document($html);
468
+
469
+ $item = $document->first('li');
470
+ $list = $document->first('ul');
471
+
472
+ $this->assertFalse($item->is($list->first('li')));
473
+ $this->assertTrue($item->is($list->firstInDocument('li')));
474
+
475
+ $this->assertCount(3, $document->find('li'));
476
+
477
+ $list->findInDocument('li')[0]->remove();
478
+
479
+ $this->assertCount(2, $document->find('li'));
480
+ }
481
+
482
+ public function testFirst()
483
+ {
484
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
485
+
486
+ $document = new Document($html);
487
+
488
+ $list = $document->first('ul');
489
+
490
+ $item = $list->getNode()->childNodes->item(0);
491
+
492
+ $this->assertEquals($item, $list->first('li')->getNode());
493
+
494
+ $list = new Element('ul');
495
+
496
+ $this->assertNull($list->first('li'));
497
+ }
498
+
499
+ /**
500
+ * @dataProvider findTests
501
+ */
502
+ public function testFindAndReturnDomElement($html, $selector, $type, $count)
503
+ {
504
+ $document = new \DOMDocument();
505
+ $document->loadHTML($html);
506
+
507
+ $node = $document->getElementsByTagName('body')->item(0);
508
+ $element = new Element($node);
509
+
510
+ $elements = $element->find($selector, $type, false);
511
+
512
+ $this->assertTrue(is_array($elements));
513
+ $this->assertEquals($count, count($elements));
514
+
515
+ foreach ($elements as $element) {
516
+ $this->assertInstanceOf('DOMElement', $element);
517
+ }
518
+ }
519
+
520
+ public function findTests()
521
+ {
522
+ $html = $this->loadFixture('posts.html');
523
+
524
+ return array(
525
+ array($html, '.post h2', Query::TYPE_CSS, 3),
526
+ array($html, '.fake h2', Query::TYPE_CSS, 0),
527
+ array($html, '.post h2, .post p', Query::TYPE_CSS, 6),
528
+ array($html, "//*[contains(concat(' ', normalize-space(@class), ' '), ' post ')]", Query::TYPE_XPATH, 3),
529
+ );
530
+ }
531
+
532
+ public function testXpath()
533
+ {
534
+ $html = $this->loadFixture('posts.html');
535
+
536
+ $document = new \DOMDocument();
537
+ $document->loadHTML($html);
538
+
539
+ $node = $document->getElementsByTagName('body')->item(0);
540
+ $element = new Element($node);
541
+
542
+ $elements = $element->xpath("//*[contains(concat(' ', normalize-space(@class), ' '), ' post ')]");
543
+
544
+ $this->assertTrue(is_array($elements));
545
+ $this->assertEquals(3, count($elements));
546
+
547
+ foreach ($elements as $element) {
548
+ $this->assertInstanceOf('DiDom\Element', $element);
549
+ }
550
+ }
551
+
552
+ public function testCount()
553
+ {
554
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
555
+
556
+ $document = new Document($html, false);
557
+ $list = $document->first('ul');
558
+
559
+ $this->assertEquals(3, $list->count('li'));
560
+
561
+ $document = new Element('ul');
562
+
563
+ $this->assertEquals(0, $document->count('li'));
564
+ }
565
+
566
+ /**
567
+ * @expectedException \InvalidArgumentException
568
+ */
569
+ public function testMatchesWithInvalidSelectorType()
570
+ {
571
+ $element = new Element('p');
572
+
573
+ $element->matches(null);
574
+ }
575
+
576
+ /**
577
+ * @expectedException \RuntimeException
578
+ */
579
+ public function testMatchesStrictWithoutTagName()
580
+ {
581
+ $element = new Element('ul', null, ['id' => 'foo', 'class' => 'bar baz']);
582
+
583
+ $element->matches('#foo.bar.baz', true);
584
+ }
585
+
586
+ public function testMatches()
587
+ {
588
+ $element = new Element('ul', null, ['id' => 'foo', 'class' => 'bar baz']);
589
+
590
+ $this->assertTrue($element->matches('ul'));
591
+ $this->assertTrue($element->matches('#foo'));
592
+ $this->assertTrue($element->matches('.bar'));
593
+ $this->assertTrue($element->matches('ul#foo.bar.baz'));
594
+ $this->assertFalse($element->matches('a#foo.bar.baz'));
595
+
596
+ // strict
597
+ $this->assertTrue($element->matches('ul#foo.bar.baz', true));
598
+ $this->assertFalse($element->matches('ul#foo.bar', true));
599
+ $this->assertFalse($element->matches('ul#foo', true));
600
+ $this->assertFalse($element->matches('ul.bar.baz', true));
601
+ $this->assertFalse($element->matches('ul.bar.baz', true));
602
+
603
+ $element = new Element('p');
604
+
605
+ $this->assertTrue($element->matches('p', true));
606
+
607
+ $html = '<!DOCTYPE html>
608
+ <html lang="en">
609
+ <head>
610
+ <meta charset="UTF-8">
611
+ <title>Document</title>
612
+ </head>
613
+ <body>
614
+ <a href="#"><img src="foo.jpg" alt="Foo"></a>
615
+ </body>
616
+ </html>';
617
+
618
+ $document = new Document($html, false);
619
+ $anchor = $document->first('a');
620
+
621
+ $this->assertTrue($anchor->matches('a:has(img[src$=".jpg"])'));
622
+ $this->assertTrue($anchor->matches('a img'));
623
+ $this->assertFalse($anchor->matches('a img[alt="Bar"]'));
624
+ $this->assertFalse($anchor->matches('img'));
625
+
626
+ $textNode = new \DOMText('Foo');
627
+ $element = new Element($textNode);
628
+
629
+ $this->assertFalse($element->matches('#foo'));
630
+
631
+ $commentNode = new \DOMComment('Foo');
632
+ $element = new Element($commentNode);
633
+
634
+ $this->assertFalse($element->matches('#foo'));
635
+ }
636
+
637
+ public function testHasAttribute()
638
+ {
639
+ $node = $this->createDomElement('input');
640
+ $element = new Element($node);
641
+
642
+ $this->assertFalse($element->hasAttribute('value'));
643
+
644
+ $node->setAttribute('value', 'test');
645
+
646
+ $this->assertTrue($element->hasAttribute('value'));
647
+ }
648
+
649
+ /**
650
+ * @expectedException \InvalidArgumentException
651
+ */
652
+ public function testSetAttributeWithInvalidValue()
653
+ {
654
+ $element = new Element('input');
655
+ $element->setAttribute('value', []);
656
+ }
657
+
658
+ public function testSetAttribute()
659
+ {
660
+ $node = $this->createDomElement('input');
661
+
662
+ $element = new Element($node);
663
+
664
+ $element->setAttribute('value', 'foo');
665
+ $this->assertEquals('foo', $element->getNode()->getAttribute('value'));
666
+
667
+ $element->setAttribute('value', 10);
668
+ $this->assertEquals('10', $element->getNode()->getAttribute('value'));
669
+
670
+ $element->setAttribute('value', 3.14);
671
+ $this->assertEquals('3.14', $element->getNode()->getAttribute('value'));
672
+ }
673
+
674
+ public function testGetAttribute()
675
+ {
676
+ $node = $this->createDomElement('input');
677
+
678
+ $element = new Element($node);
679
+
680
+ $this->assertEquals(null, $element->getAttribute('value'));
681
+ $this->assertEquals('default', $element->getAttribute('value', 'default'));
682
+
683
+ $node->setAttribute('value', 'test');
684
+
685
+ $this->assertEquals('test', $element->getAttribute('value'));
686
+ }
687
+
688
+ public function testRemoveAttribute()
689
+ {
690
+ $domElement = $this->createDomElement('input', null, ['name' => 'username']);
691
+
692
+ $element = new Element($domElement);
693
+
694
+ $this->assertTrue($element->getNode()->hasAttribute('name'));
695
+
696
+ $result = $element->removeAttribute('name');
697
+
698
+ $this->assertEquals(0, $element->getNode()->attributes->length);
699
+ $this->assertFalse($element->getNode()->hasAttribute('name'));
700
+ $this->assertEquals($result, $element);
701
+ }
702
+
703
+ public function testRemoveAllAttributes()
704
+ {
705
+ $attributes = ['type' => 'text', 'name' => 'username'];
706
+
707
+ $domElement = $this->createDomElement('input', null, $attributes);
708
+
709
+ $element = new Element($domElement);
710
+
711
+ $result = $element->removeAllAttributes();
712
+
713
+ $this->assertEquals(0, $element->getNode()->attributes->length);
714
+ $this->assertEquals($result, $element);
715
+ }
716
+
717
+ public function testRemoveAllAttributesWithExclusion()
718
+ {
719
+ $attributes = ['type' => 'text', 'name' => 'username'];
720
+
721
+ $domElement = $this->createDomElement('input', null, $attributes);
722
+
723
+ $element = new Element($domElement);
724
+
725
+ $element->removeAllAttributes(['name']);
726
+
727
+ $this->assertEquals(1, $element->getNode()->attributes->length);
728
+ $this->assertEquals('username', $element->getNode()->getAttribute('name'));
729
+ }
730
+
731
+ public function testAttrSet()
732
+ {
733
+ $element = new Element('input');
734
+
735
+ $element->attr('name', 'username');
736
+
737
+ $this->assertEquals('username', $element->getNode()->getAttribute('name'));
738
+ }
739
+
740
+ public function testAttrGet()
741
+ {
742
+ $element = new Element('input', null, ['name' => 'username']);
743
+
744
+ $this->assertEquals('username', $element->attr('name'));
745
+ }
746
+
747
+ public function testAttributes()
748
+ {
749
+ $attributes = ['type' => 'text', 'name' => 'username', 'value' => 'John'];
750
+
751
+ $domElement = $this->createDomElement('input', null, $attributes);
752
+
753
+ $element = new Element($domElement);
754
+
755
+ $this->assertEquals($attributes, $element->attributes());
756
+ $this->assertEquals(['name' => 'username', 'value' => 'John'], $element->attributes(['name', 'value']));
757
+ }
758
+
759
+
760
+ public function testAttributesWithText()
761
+ {
762
+ $element = new Element(new \DOMText('Foo'));
763
+
764
+ $this->assertNull($element->attributes());
765
+ }
766
+
767
+
768
+ public function testAttributesWithComment()
769
+ {
770
+ $element = new Element(new \DOMComment('Foo'));
771
+
772
+ $this->assertNull($element->attributes());
773
+ }
774
+
775
+ /**
776
+ * @expectedException \LogicException
777
+ * @expectedExceptionMessage Style attribute is available only for element nodes
778
+ */
779
+ public function testStyleWithTextNode()
780
+ {
781
+ $element = new Element(new \DOMText('foo'));
782
+
783
+ $element->style();
784
+ }
785
+
786
+ /**
787
+ * @expectedException \LogicException
788
+ * @expectedExceptionMessage Style attribute is available only for element nodes
789
+ */
790
+ public function testStyleWithCommentNode()
791
+ {
792
+ $element = new Element(new \DOMComment('foo'));
793
+
794
+ $element->style();
795
+ }
796
+
797
+ public function testStyle()
798
+ {
799
+ $element = new Element('div');
800
+
801
+ $styleAttribute = $element->style();
802
+
803
+ $this->assertInstanceOf('DiDom\\StyleAttribute', $styleAttribute);
804
+ $this->assertSame($element, $styleAttribute->getElement());
805
+
806
+ $this->assertSame($styleAttribute, $element->style());
807
+
808
+ $element2 = new Element('div');
809
+
810
+ $this->assertNotSame($element->style(), $element2->style());
811
+ }
812
+
813
+ public function testHtml()
814
+ {
815
+ $element = new Element('span', 'hello');
816
+
817
+ $this->assertEquals('<span>hello</span>', $element->html());
818
+ }
819
+
820
+ public function testOuterHtml()
821
+ {
822
+ $innerHtml = 'Plain text <span>Lorem ipsum.</span><span>Lorem ipsum.</span>';
823
+ $html = "<div id=\"foo\" class=\"bar baz\">$innerHtml</div>";
824
+
825
+ $document = new Document($html);
826
+
827
+ $this->assertEquals('<div id="foo" class="bar baz"></div>', $document->first('#foo')->outerHtml());
828
+ }
829
+
830
+ public function testInnerHtml()
831
+ {
832
+ $innerHtml = ' Plain text <span>Lorem ipsum.</span><span>Lorem ipsum.</span>';
833
+ $html = "<div id=\"root\">$innerHtml</div>";
834
+
835
+ $document = new Document($html);
836
+
837
+ $this->assertEquals($innerHtml, $document->first('#root')->innerHtml());
838
+
839
+ $html = '
840
+ <!DOCTYPE html>
841
+ <html lang="en">
842
+ <head>
843
+ <meta charset="UTF-8">
844
+ <title>Document</title>
845
+ </head>
846
+ <body>
847
+ English language <br>
848
+ Русский язык <br>
849
+ اللغة العربية <br>
850
+ 漢語 <br>
851
+ Tiếng Việt <br>
852
+
853
+ &lt; &gt;
854
+ </body>
855
+ </html>
856
+ ';
857
+
858
+ $expectedContent = '
859
+ English language <br>
860
+ Русский язык <br>
861
+ اللغة العربية <br>
862
+ 漢語 <br>
863
+ Tiếng Việt <br>
864
+
865
+ &lt; &gt;
866
+ ';
867
+
868
+ $document = new Document($html);
869
+
870
+ $this->assertEquals($expectedContent, $document->first('body')->innerHtml());
871
+ }
872
+
873
+ public function testSetInnerHtml()
874
+ {
875
+ $list = new Element('ul');
876
+
877
+ $html = '<li>One</li><li>Two</li><li>Three</li>';
878
+
879
+ $this->assertEquals($list, $list->setInnerHtml($html));
880
+ $this->assertEquals(['One', 'Two', 'Three'], $list->find('li::text'));
881
+
882
+ // check inner HTML rewrite works
883
+
884
+ $html = '<li>Foo</li><li>Bar</li><li>Baz</li>';
885
+
886
+ $this->assertEquals($list, $list->setInnerHtml($html));
887
+ $this->assertEquals(['Foo', 'Bar', 'Baz'], $list->find('li::text'));
888
+
889
+ $html = '<div id="root"></div>';
890
+ $innerHtml = ' Plain text <span>Lorem ipsum.</span><span>Lorem ipsum.</span>';
891
+
892
+ $document = new Document($html, false);
893
+
894
+ $document->first('#root')->setInnerHtml($innerHtml);
895
+
896
+ $this->assertEquals($innerHtml, $document->first('#root')->innerHtml());
897
+ }
898
+
899
+ public function testXml()
900
+ {
901
+ $element = new Element('span', 'hello');
902
+
903
+ $prolog = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
904
+
905
+ $this->assertEquals($prolog.'<span>hello</span>', $element->xml());
906
+ }
907
+
908
+ public function testXmlWithOptions()
909
+ {
910
+ $html = '<html><body><span></span></body></html>';
911
+
912
+ $document = new Document();
913
+ $document->loadHtml($html);
914
+
915
+ $element = $document->find('span')[0];
916
+
917
+ $prolog = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
918
+
919
+ $this->assertEquals($prolog.'<span/>', $element->xml());
920
+ $this->assertEquals($prolog.'<span></span>', $element->xml(LIBXML_NOEMPTYTAG));
921
+ }
922
+
923
+ public function testGetText()
924
+ {
925
+ $element = new Element('span', 'hello');
926
+
927
+ $this->assertEquals('hello', $element->text());
928
+ }
929
+
930
+ public function testSetValue()
931
+ {
932
+ $element = new Element('span', 'hello');
933
+ $element->setValue('test');
934
+
935
+ $this->assertEquals('test', $element->text());
936
+ }
937
+
938
+ public function testIsElementNode()
939
+ {
940
+ $element = new Element('div');
941
+
942
+ $element->setInnerHtml(' Foo <span>Bar</span><!-- Baz -->');
943
+
944
+ $children = $element->children();
945
+
946
+ $this->assertFalse($children[0]->isElementNode());
947
+ $this->assertTrue($children[1]->isElementNode());
948
+ $this->assertFalse($children[2]->isElementNode());
949
+ }
950
+
951
+ public function testIsTextNode()
952
+ {
953
+ $element = new Element('div');
954
+
955
+ $element->setInnerHtml(' Foo <span>Bar</span><!-- Baz -->');
956
+
957
+ $children = $element->children();
958
+
959
+ $this->assertTrue($children[0]->isTextNode());
960
+ $this->assertFalse($children[1]->isTextNode());
961
+ $this->assertFalse($children[2]->isTextNode());
962
+ }
963
+
964
+ public function testIsCommentNode()
965
+ {
966
+ $element = new Element('div');
967
+
968
+ $element->setInnerHtml(' Foo <span>Bar</span><!-- Baz -->');
969
+
970
+ $children = $element->children();
971
+
972
+ $this->assertFalse($children[0]->isCommentNode());
973
+ $this->assertFalse($children[1]->isCommentNode());
974
+ $this->assertTrue($children[2]->isCommentNode());
975
+ }
976
+
977
+ public function testIs()
978
+ {
979
+ $element = new Element('span', 'hello');
980
+ $element2 = new Element('span', 'hello');
981
+
982
+ $this->assertTrue($element->is($element));
983
+ $this->assertFalse($element->is($element2));
984
+ }
985
+
986
+ /**
987
+ * @expectedException \InvalidArgumentException
988
+ */
989
+ public function testIsWithInvalidArgument()
990
+ {
991
+ $element = new Element('span', 'hello');
992
+ $element->is(null);
993
+ }
994
+
995
+ public function testParent()
996
+ {
997
+ $html = $this->loadFixture('posts.html');
998
+ $document = new Document($html, false);
999
+ $element = $document->createElement('span', 'value');
1000
+
1001
+ $this->assertEquals($document->getDocument(), $element->getDocument()->getDocument());
1002
+ }
1003
+
1004
+ public function testClosest()
1005
+ {
1006
+ // without body and html tags
1007
+ $html = '
1008
+ <nav>
1009
+ <ul class="menu">
1010
+ <li><a href="#">Foo</a></li>
1011
+ <li><a href="#">Bar</a></li>
1012
+ <li><a href="#">Baz</a></li>
1013
+ </ul>
1014
+ </nav>
1015
+ ';
1016
+
1017
+ $document = new Document($html);
1018
+
1019
+ $menu = $document->first('.menu');
1020
+ $link = $document->first('a');
1021
+
1022
+ $this->assertNull($link->closest('.unknown-class'));
1023
+ $this->assertEquals($menu, $link->closest('.menu'));
1024
+
1025
+ $html = '<!DOCTYPE html>
1026
+ <html>
1027
+ <body>
1028
+ <nav></nav>
1029
+ <ul class="menu">
1030
+ <li><a href="#">Foo</a></li>
1031
+ <li><a href="#">Bar</a></li>
1032
+ <li><a href="#">Baz</a></li>
1033
+ </ul>
1034
+ </body>
1035
+ </html>';
1036
+
1037
+ $document = new Document($html);
1038
+
1039
+ $this->assertNull($document->first('ul.menu')->closest('nav'));
1040
+ }
1041
+
1042
+ // =========================
1043
+ // previousSibling
1044
+ // =========================
1045
+
1046
+ public function testPreviousSibling()
1047
+ {
1048
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
1049
+
1050
+ $document = new Document($html, false);
1051
+
1052
+ $list = $document->first('ul')->getNode();
1053
+
1054
+ $item = $list->childNodes->item(0);
1055
+ $item = new Element($item);
1056
+
1057
+ $this->assertNull($item->previousSibling());
1058
+
1059
+ $item = $list->childNodes->item(1);
1060
+ $item = new Element($item);
1061
+
1062
+ $expectedNode = $list->childNodes->item(0);
1063
+
1064
+ $this->assertEquals($expectedNode, $item->previousSibling()->getNode());
1065
+ }
1066
+
1067
+ public function testPreviousSiblingWithTextNode()
1068
+ {
1069
+ $html = '<p>Foo <span>Bar</span> Baz</p>';
1070
+
1071
+ $document = new Document($html, false);
1072
+
1073
+ $span = $document->first('span');
1074
+
1075
+ $expectedNode = $span->getNode()->previousSibling;
1076
+
1077
+ $this->assertEquals($expectedNode, $span->previousSibling()->getNode());
1078
+ }
1079
+
1080
+ public function testPreviousSiblingWithCommentNode()
1081
+ {
1082
+ $html = '<p><!-- Foo --><span>Bar</span> Baz</p>';
1083
+
1084
+ $document = new Document($html, false);
1085
+
1086
+ $span = $document->first('span');
1087
+
1088
+ $expectedNode = $span->getNode()->previousSibling;
1089
+
1090
+ $this->assertEquals($expectedNode, $span->previousSibling()->getNode());
1091
+ }
1092
+
1093
+ public function testPreviousSiblingWithSelector()
1094
+ {
1095
+ $html =
1096
+ '<ul>'.
1097
+ '<li><a href="https://amazon.com">Amazon</a></li>'.
1098
+ '<li><a href="https://facebook.com">Facebook</a></li>'.
1099
+ '<li><a href="https://google.com">Google</a></li>'.
1100
+ '<li><a href="https://www.w3.org">W3C</a></li>'.
1101
+ '<li><a href="https://wikipedia.org">Wikipedia</a></li>'.
1102
+ '</ul>'
1103
+ ;
1104
+
1105
+ $document = new Document($html, false);
1106
+
1107
+ $list = $document->first('ul');
1108
+
1109
+ $item = $list->getNode()->childNodes->item(4);
1110
+ $item = new Element($item);
1111
+
1112
+ $expectedNode = $list->getNode()->childNodes->item(2);
1113
+
1114
+ $this->assertEquals($expectedNode, $item->previousSibling('li:has(a[href$=".com"])')->getNode());
1115
+ }
1116
+
1117
+ public function testPreviousSiblingWithNodeType()
1118
+ {
1119
+ $html = '<p>Foo <span>Bar</span><!--qwe--> Baz <span>Qux</span></p>';
1120
+
1121
+ $document = new Document($html, false);
1122
+
1123
+ $paragraph = $document->first('p');
1124
+ $span = $document->find('span')[1];
1125
+
1126
+ $expectedNode = $paragraph->getNode()->childNodes->item(1);
1127
+ $this->assertEquals($expectedNode, $span->previousSibling(null, 'DOMElement')->getNode());
1128
+
1129
+ $expectedNode = $paragraph->getNode()->childNodes->item(2);
1130
+ $this->assertEquals($expectedNode, $span->previousSibling(null, 'DOMComment')->getNode());
1131
+ }
1132
+
1133
+ /**
1134
+ * @expectedException \InvalidArgumentException
1135
+ */
1136
+ public function testPreviousSiblingWithInvalidTypeOfNodeTypeArgument()
1137
+ {
1138
+ $html = '<p>Foo <span>Bar</span><!--qwe--> Baz <span>Qux</span></p>';
1139
+
1140
+ $document = new Document($html, false);
1141
+
1142
+ $span = $document->find('span')[1];
1143
+
1144
+ $span->previousSibling(null, []);
1145
+ }
1146
+
1147
+ /**
1148
+ * @expectedException \RuntimeException
1149
+ */
1150
+ public function testPreviousSiblingWithInvalidNodeType()
1151
+ {
1152
+ $html = '<p>Foo <span>Bar</span><!--qwe--> Baz <span>Qux</span></p>';
1153
+
1154
+ $document = new Document($html, false);
1155
+
1156
+ $span = $document->find('span')[1];
1157
+
1158
+ $span->previousSibling(null, 'foo');
1159
+ }
1160
+
1161
+ /**
1162
+ * @dataProvider previousSiblingWithSelectorAndNotDomElementNodeTypeDataProvider
1163
+ *
1164
+ * @expectedException \LogicException
1165
+ */
1166
+ public function testPreviousSiblingWithSelectorAndNotDomElement($nodeType)
1167
+ {
1168
+ $html =
1169
+ '<ul>'.
1170
+ '<li><a href="https://amazon.com">Amazon</a></li>'.
1171
+ '<li><a href="https://facebook.com">Facebook</a></li>'.
1172
+ '<li><a href="https://google.com">Google</a></li>'.
1173
+ '<li><a href="https://www.w3.org">W3C</a></li>'.
1174
+ '<li><a href="https://wikipedia.org">Wikipedia</a></li>'.
1175
+ '</ul>'
1176
+ ;
1177
+
1178
+ $document = new Document($html, false);
1179
+
1180
+ $list = $document->first('ul');
1181
+
1182
+ $item = $list->getNode()->childNodes->item(4);
1183
+ $item = new Element($item);
1184
+
1185
+ $item->previousSibling('li:has(a[href$=".com"])', $nodeType);
1186
+ }
1187
+
1188
+ public function previousSiblingWithSelectorAndNotDomElementNodeTypeDataProvider()
1189
+ {
1190
+ return [['DOMText'], ['DOMComment']];
1191
+ }
1192
+
1193
+ // =========================
1194
+ // previousSiblings
1195
+ // =========================
1196
+
1197
+ public function testPreviousSiblings()
1198
+ {
1199
+ $html = '<p>Foo <span>Bar</span> Baz <span>Qux</span></p>';
1200
+
1201
+ $document = new Document($html, false);
1202
+
1203
+ $paragraph = $document->first('p');
1204
+ $span = $paragraph->find('span')[1];
1205
+
1206
+ $childNodes = $paragraph->getNode()->childNodes;
1207
+
1208
+ $expectedResult = [
1209
+ $childNodes->item(0),
1210
+ $childNodes->item(1),
1211
+ $childNodes->item(2),
1212
+ ];
1213
+
1214
+ $previousSiblings = $span->previousSiblings();
1215
+
1216
+ $this->assertCount(count($expectedResult), $previousSiblings);
1217
+
1218
+ foreach ($previousSiblings as $index => $previousSibling) {
1219
+ $this->assertEquals($expectedResult[$index], $previousSibling->getNode());
1220
+ }
1221
+ }
1222
+
1223
+ public function testPreviousSiblingsWithSelector()
1224
+ {
1225
+ $html =
1226
+ '<ul>'.
1227
+ '<li><a href="https://amazon.com">Amazon</a></li>'.
1228
+ '<li><a href="https://facebook.com">Facebook</a></li>'.
1229
+ '<li><a href="https://google.com">Google</a></li>'.
1230
+ '<li><a href="https://www.w3.org">W3C</a></li>'.
1231
+ '<li><a href="https://wikipedia.org">Wikipedia</a></li>'.
1232
+ '</ul>'
1233
+ ;
1234
+
1235
+ $document = new Document($html, false);
1236
+
1237
+ $list = $document->first('ul');
1238
+
1239
+ $item = $list->getNode()->childNodes->item(4);
1240
+ $item = new Element($item);
1241
+
1242
+ $childNodes = $list->getNode()->childNodes;
1243
+
1244
+ $expectedResult = [
1245
+ $childNodes->item(0),
1246
+ $childNodes->item(1),
1247
+ $childNodes->item(2),
1248
+ ];
1249
+
1250
+ $previousSiblings = $item->previousSiblings('li:has(a[href$=".com"])');
1251
+
1252
+ $this->assertCount(count($expectedResult), $previousSiblings);
1253
+
1254
+ foreach ($previousSiblings as $index => $previousSibling) {
1255
+ $this->assertEquals($expectedResult[$index], $previousSibling->getNode());
1256
+ }
1257
+ }
1258
+
1259
+ public function testPreviousSiblingsWithNodeType()
1260
+ {
1261
+ $html = '<p>Foo <span>Bar</span><!--qwe--> Baz <span>Qux</span></p>';
1262
+
1263
+ $document = new Document($html, false);
1264
+
1265
+ $paragraph = $document->first('p');
1266
+ $span = $document->find('span')[1];
1267
+
1268
+ $childNodes = $paragraph->getNode()->childNodes;
1269
+
1270
+ $expectedResult = [
1271
+ $childNodes->item(1),
1272
+ ];
1273
+
1274
+ $previousSiblings = $span->previousSiblings(null, 'DOMElement');
1275
+
1276
+ $this->assertCount(count($expectedResult), $previousSiblings);
1277
+
1278
+ foreach ($previousSiblings as $index => $previousSibling) {
1279
+ $this->assertEquals($expectedResult[$index], $previousSibling->getNode());
1280
+ }
1281
+
1282
+ $expectedResult = [
1283
+ $childNodes->item(2),
1284
+ ];
1285
+
1286
+ $previousSiblings = $span->previousSiblings(null, 'DOMComment');
1287
+
1288
+ $this->assertCount(count($expectedResult), $previousSiblings);
1289
+
1290
+ foreach ($previousSiblings as $index => $previousSibling) {
1291
+ $this->assertEquals($expectedResult[$index], $previousSibling->getNode());
1292
+ }
1293
+ }
1294
+
1295
+ /**
1296
+ * @expectedException \InvalidArgumentException
1297
+ */
1298
+ public function testPreviousSiblingsWithInvalidTypeOfNodeTypeArgument()
1299
+ {
1300
+ $html = '<p>Foo <span>Bar</span><!--qwe--> Baz <span>Qux</span></p>';
1301
+
1302
+ $document = new Document($html, false);
1303
+
1304
+ $span = $document->find('span')[1];
1305
+
1306
+ $span->previousSiblings(null, []);
1307
+ }
1308
+
1309
+ /**
1310
+ * @expectedException \RuntimeException
1311
+ */
1312
+ public function testPreviousSiblingsWithInvalidNodeType()
1313
+ {
1314
+ $html = '<p>Foo <span>Bar</span><!--qwe--> Baz <span>Qux</span></p>';
1315
+
1316
+ $document = new Document($html, false);
1317
+
1318
+ $span = $document->find('span')[1];
1319
+
1320
+ $span->previousSibling(null, 'foo');
1321
+ }
1322
+
1323
+ /**
1324
+ * @dataProvider previousSiblingsWithSelectorAndNotDomElementNodeTypeDataProvider
1325
+ *
1326
+ * @expectedException \LogicException
1327
+ */
1328
+ public function testPreviousSiblingsWithSelectorAndNotDomElement($nodeType)
1329
+ {
1330
+ $html =
1331
+ '<ul>'.
1332
+ '<li><a href="https://amazon.com">Amazon</a></li>'.
1333
+ '<li><a href="https://facebook.com">Facebook</a></li>'.
1334
+ '<li><a href="https://google.com">Google</a></li>'.
1335
+ '<li><a href="https://www.w3.org">W3C</a></li>'.
1336
+ '<li><a href="https://wikipedia.org">Wikipedia</a></li>'.
1337
+ '</ul>'
1338
+ ;
1339
+
1340
+ $document = new Document($html, false);
1341
+
1342
+ $list = $document->first('ul');
1343
+
1344
+ $item = $list->getNode()->childNodes->item(4);
1345
+ $item = new Element($item);
1346
+
1347
+ $item->previousSiblings('li:has(a[href$=".com"])', $nodeType);
1348
+ }
1349
+
1350
+ public function previousSiblingsWithSelectorAndNotDomElementNodeTypeDataProvider()
1351
+ {
1352
+ return [['DOMText'], ['DOMComment']];
1353
+ }
1354
+
1355
+ // =========================
1356
+ // nextSibling
1357
+ // =========================
1358
+
1359
+ public function testNextSibling()
1360
+ {
1361
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
1362
+
1363
+ $document = new Document($html, false);
1364
+
1365
+ $list = $document->first('ul');
1366
+
1367
+ $item = $list->getNode()->childNodes->item(2);
1368
+ $item = new Element($item);
1369
+
1370
+ $this->assertNull($item->nextSibling());
1371
+
1372
+ $item = $list->getNode()->childNodes->item(0);
1373
+ $item = new Element($item);
1374
+
1375
+ $expectedNode = $list->getNode()->childNodes->item(1);
1376
+
1377
+ $this->assertEquals($expectedNode, $item->nextSibling()->getNode());
1378
+ }
1379
+
1380
+ public function testNextSiblingWithTextNode()
1381
+ {
1382
+ $html = '<p>Foo <span>Bar</span> Baz</p>';
1383
+
1384
+ $document = new Document($html, false);
1385
+
1386
+ $paragraph = $document->first('p');
1387
+ $span = $paragraph->first('span');
1388
+
1389
+ $expectedNode = $span->getNode()->nextSibling;
1390
+
1391
+ $this->assertEquals($expectedNode, $span->nextSibling()->getNode());
1392
+ }
1393
+
1394
+ public function testNextSiblingWithCommentNode()
1395
+ {
1396
+ $html = '<p>Foo <span>Bar</span><!-- Baz --></p>';
1397
+
1398
+ $document = new Document($html, false);
1399
+
1400
+ $paragraph = $document->first('p');
1401
+ $span = $paragraph->first('span');
1402
+
1403
+ $expectedNode = $span->getNode()->nextSibling;
1404
+
1405
+ $this->assertEquals($expectedNode, $span->nextSibling()->getNode());
1406
+ }
1407
+
1408
+ public function testNextSiblingWithSelector()
1409
+ {
1410
+ $html =
1411
+ '<ul>'.
1412
+ '<li><a href="https://amazon.com">Amazon</a></li>'.
1413
+ '<li><a href="https://facebook.com">Facebook</a></li>'.
1414
+ '<li><a href="https://google.com">Google</a></li>'.
1415
+ '<li><a href="https://www.w3.org">W3C</a></li>'.
1416
+ '<li><a href="https://wikipedia.org">Wikipedia</a></li>'.
1417
+ '</ul>'
1418
+ ;
1419
+
1420
+ $document = new Document($html, false);
1421
+
1422
+ $list = $document->first('ul');
1423
+
1424
+ $item = $list->getNode()->childNodes->item(0);
1425
+ $item = new Element($item);
1426
+
1427
+ $expectedNode = $list->getNode()->childNodes->item(3);
1428
+
1429
+ $this->assertEquals($expectedNode, $item->nextSibling('li:has(a[href$=".org"])')->getNode());
1430
+ }
1431
+
1432
+ public function testNextSiblingWithNodeType()
1433
+ {
1434
+ $html = '<p>Foo <span>Bar</span> Baz <!--qwe--><span>Qux</span></p>';
1435
+
1436
+ $document = new Document($html, false);
1437
+
1438
+ $paragraph = $document->first('p');
1439
+ $span = $document->find('span')[0];
1440
+
1441
+ $expectedNode = $paragraph->getNode()->childNodes->item(4);
1442
+ $this->assertEquals($expectedNode, $span->nextSibling(null, 'DOMElement')->getNode());
1443
+
1444
+ $expectedNode = $paragraph->getNode()->childNodes->item(3);
1445
+ $this->assertEquals($expectedNode, $span->nextSibling(null, 'DOMComment')->getNode());
1446
+ }
1447
+
1448
+ /**
1449
+ * @expectedException \InvalidArgumentException
1450
+ */
1451
+ public function testNextSiblingWithInvalidTypeOfNodeTypeArgument()
1452
+ {
1453
+ $html = '<p>Foo <span>Bar</span> Baz <!--qwe--><span>Qux</span></p>';
1454
+
1455
+ $document = new Document($html, false);
1456
+
1457
+ $span = $document->find('span')[0];
1458
+
1459
+ $span->nextSibling(null, []);
1460
+ }
1461
+
1462
+ /**
1463
+ * @expectedException \RuntimeException
1464
+ */
1465
+ public function testNextSiblingWithInvalidNodeType()
1466
+ {
1467
+ $html = '<p>Foo <span>Bar</span> Baz <!--qwe--><span>Qux</span></p>';
1468
+
1469
+ $document = new Document($html, false);
1470
+
1471
+ $span = $document->find('span')[0];
1472
+
1473
+ $span->nextSibling(null, 'foo');
1474
+ }
1475
+
1476
+ /**
1477
+ * @dataProvider nextSiblingWithSelectorAndNotDomElementNodeTypeDataProvider
1478
+ *
1479
+ * @expectedException \LogicException
1480
+ */
1481
+ public function testNextSiblingWithSelectorAndNotDomElement($nodeType)
1482
+ {
1483
+ $html =
1484
+ '<ul>'.
1485
+ '<li><a href="https://amazon.com">Amazon</a></li>'.
1486
+ '<li><a href="https://facebook.com">Facebook</a></li>'.
1487
+ '<li><a href="https://google.com">Google</a></li>'.
1488
+ '<li><a href="https://www.w3.org">W3C</a></li>'.
1489
+ '<li><a href="https://wikipedia.org">Wikipedia</a></li>'.
1490
+ '</ul>'
1491
+ ;
1492
+
1493
+ $document = new Document($html, false);
1494
+
1495
+ $list = $document->first('ul');
1496
+
1497
+ $item = $list->getNode()->childNodes->item(0);
1498
+ $item = new Element($item);
1499
+
1500
+ $item->nextSibling('li:has(a[href$=".com"])', $nodeType);
1501
+ }
1502
+
1503
+ public function nextSiblingWithSelectorAndNotDomElementNodeTypeDataProvider()
1504
+ {
1505
+ return [['DOMText'], ['DOMComment']];
1506
+ }
1507
+
1508
+ // =========================
1509
+ // nextSiblings
1510
+ // =========================
1511
+
1512
+ public function testNextSiblings()
1513
+ {
1514
+ $html = '<p>Foo <span>Bar</span> Baz <span>Qux</span></p>';
1515
+
1516
+ $document = new Document($html, false);
1517
+
1518
+ $paragraph = $document->first('p');
1519
+ $span = $paragraph->find('span')[0];
1520
+
1521
+ $childNodes = $paragraph->getNode()->childNodes;
1522
+
1523
+ $expectedResult = [
1524
+ $childNodes->item(2),
1525
+ $childNodes->item(3),
1526
+ ];
1527
+
1528
+ $nextSiblings = $span->nextSiblings();
1529
+
1530
+ $this->assertCount(count($expectedResult), $nextSiblings);
1531
+
1532
+ foreach ($nextSiblings as $index => $nextSibling) {
1533
+ $this->assertEquals($expectedResult[$index], $nextSibling->getNode());
1534
+ }
1535
+ }
1536
+
1537
+ public function testNextSiblingsWithSelector()
1538
+ {
1539
+ $html =
1540
+ '<ul>'.
1541
+ '<li><a href="https://amazon.com">Amazon</a></li>'.
1542
+ '<li><a href="https://facebook.com">Facebook</a></li>'.
1543
+ '<li><a href="https://google.com">Google</a></li>'.
1544
+ '<li><a href="https://www.w3.org">W3C</a></li>'.
1545
+ '<li><a href="https://wikipedia.org">Wikipedia</a></li>'.
1546
+ '</ul>'
1547
+ ;
1548
+
1549
+ $document = new Document($html, false);
1550
+
1551
+ $list = $document->first('ul');
1552
+
1553
+ $item = $list->getNode()->childNodes->item(0);
1554
+ $item = new Element($item);
1555
+
1556
+ $childNodes = $list->getNode()->childNodes;
1557
+
1558
+ $expectedResult = [
1559
+ $childNodes->item(1),
1560
+ $childNodes->item(2),
1561
+ ];
1562
+
1563
+ $nextSiblings = $item->nextSiblings('li:has(a[href$=".com"])');
1564
+
1565
+ $this->assertCount(count($expectedResult), $nextSiblings);
1566
+
1567
+ foreach ($nextSiblings as $index => $nextSibling) {
1568
+ $this->assertEquals($expectedResult[$index], $nextSibling->getNode());
1569
+ }
1570
+ }
1571
+
1572
+ public function testNextSiblingsWithNodeType()
1573
+ {
1574
+ $html = '<p>Foo <span>Bar</span><!--qwe--> Baz <span>Qux</span></p>';
1575
+
1576
+ $document = new Document($html, false);
1577
+
1578
+ $paragraph = $document->first('p');
1579
+ $span = $document->find('span')[0];
1580
+
1581
+ $childNodes = $paragraph->getNode()->childNodes;
1582
+
1583
+ $expectedResult = [
1584
+ $childNodes->item(4),
1585
+ ];
1586
+
1587
+ $previousSiblings = $span->nextSiblings(null, 'DOMElement');
1588
+
1589
+ $this->assertCount(count($expectedResult), $previousSiblings);
1590
+
1591
+ foreach ($previousSiblings as $index => $previousSibling) {
1592
+ $this->assertEquals($expectedResult[$index], $previousSibling->getNode());
1593
+ }
1594
+
1595
+ $expectedResult = [
1596
+ $childNodes->item(2),
1597
+ ];
1598
+
1599
+ $previousSiblings = $span->nextSiblings(null, 'DOMComment');
1600
+
1601
+ $this->assertCount(count($expectedResult), $previousSiblings);
1602
+
1603
+ foreach ($previousSiblings as $index => $previousSibling) {
1604
+ $this->assertEquals($expectedResult[$index], $previousSibling->getNode());
1605
+ }
1606
+ }
1607
+
1608
+ /**
1609
+ * @expectedException \InvalidArgumentException
1610
+ */
1611
+ public function testNextSiblingsWithInvalidTypeOfNodeTypeArgument()
1612
+ {
1613
+ $html = '<p>Foo <span>Bar</span><!--qwe--> Baz <span>Qux</span></p>';
1614
+
1615
+ $document = new Document($html, false);
1616
+
1617
+ $span = $document->find('span')[0];
1618
+
1619
+ $span->nextSiblings(null, []);
1620
+ }
1621
+
1622
+ /**
1623
+ * @expectedException \RuntimeException
1624
+ */
1625
+ public function testNextSiblingsWithInvalidNodeType()
1626
+ {
1627
+ $html = '<p>Foo <span>Bar</span><!--qwe--> Baz <span>Qux</span></p>';
1628
+
1629
+ $document = new Document($html, false);
1630
+
1631
+ $span = $document->find('span')[0];
1632
+
1633
+ $span->nextSiblings(null, 'foo');
1634
+ }
1635
+
1636
+ /**
1637
+ * @dataProvider nextSiblingsWithSelectorAndNotDomElementNodeTypeDataProvider
1638
+ *
1639
+ * @expectedException \LogicException
1640
+ */
1641
+ public function testNextSiblingsWithSelectorAndNotDomElement($nodeType)
1642
+ {
1643
+ $html =
1644
+ '<ul>'.
1645
+ '<li><a href="https://amazon.com">Amazon</a></li>'.
1646
+ '<li><a href="https://facebook.com">Facebook</a></li>'.
1647
+ '<li><a href="https://google.com">Google</a></li>'.
1648
+ '<li><a href="https://www.w3.org">W3C</a></li>'.
1649
+ '<li><a href="https://wikipedia.org">Wikipedia</a></li>'.
1650
+ '</ul>'
1651
+ ;
1652
+
1653
+ $document = new Document($html, false);
1654
+
1655
+ $list = $document->first('ul');
1656
+
1657
+ $item = $list->getNode()->childNodes->item(0);
1658
+ $item = new Element($item);
1659
+
1660
+ $item->nextSiblings('li:has(a[href$=".com"])', $nodeType);
1661
+ }
1662
+
1663
+ public function nextSiblingsWithSelectorAndNotDomElementNodeTypeDataProvider()
1664
+ {
1665
+ return [['DOMText'], ['DOMComment']];
1666
+ }
1667
+
1668
+ public function testChild()
1669
+ {
1670
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
1671
+
1672
+ $document = new Document($html, false);
1673
+
1674
+ $list = $document->first('ul');
1675
+
1676
+ $this->assertEquals($list->getNode()->childNodes->item(0), $list->child(0)->getNode());
1677
+ $this->assertEquals($list->getNode()->childNodes->item(2), $list->child(2)->getNode());
1678
+ $this->assertNull($list->child(3));
1679
+
1680
+ // with text nodes
1681
+ $html = '<p>Foo <span>Bar</span> Baz</p>';
1682
+
1683
+ $document = new Document($html, false);
1684
+
1685
+ $paragraph = $document->first('p');
1686
+
1687
+ $child = $paragraph->getNode()->childNodes->item(0);
1688
+
1689
+ $this->assertEquals($child, $paragraph->child(0)->getNode());
1690
+ }
1691
+
1692
+ public function testFirstChild()
1693
+ {
1694
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
1695
+
1696
+ $document = new Document($html, false);
1697
+
1698
+ $list = $document->first('ul');
1699
+
1700
+ $this->assertEquals($list->getNode()->firstChild, $list->firstChild()->getNode());
1701
+
1702
+ $list = new Element('ul');
1703
+
1704
+ $this->assertNull($list->firstChild());
1705
+
1706
+ // with text nodes
1707
+ $html = '<p>Foo <span>Bar</span> Baz</p>';
1708
+
1709
+ $document = new Document($html, false);
1710
+
1711
+ $paragraph = $document->first('p');
1712
+
1713
+ $firstChild = $paragraph->getNode()->firstChild;
1714
+
1715
+ $this->assertEquals($firstChild, $paragraph->firstChild()->getNode());
1716
+ }
1717
+
1718
+ public function testLastChild()
1719
+ {
1720
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
1721
+
1722
+ $document = new Document($html, false);
1723
+ $list = $document->first('ul');
1724
+
1725
+ $this->assertEquals($list->getNode()->lastChild, $list->lastChild()->getNode());
1726
+
1727
+ $list = new Element('ul');
1728
+
1729
+ $this->assertNull($list->lastChild());
1730
+
1731
+ // with text nodes
1732
+ $html = '<p>Foo <span>Bar</span> Baz</p>';
1733
+
1734
+ $document = new Document($html, false);
1735
+ $paragraph = $document->first('p');
1736
+
1737
+ $lastChild = $paragraph->getNode()->lastChild;
1738
+
1739
+ $this->assertEquals($lastChild, $paragraph->lastChild()->getNode());
1740
+ }
1741
+
1742
+ public function testHasChildren()
1743
+ {
1744
+ $html = '
1745
+ <p class="element"><br></p>
1746
+ <p class="text">Foo</p>
1747
+ <p class="comment"><!-- Foo --></p>
1748
+ <p class="empty"></p>
1749
+ ';
1750
+
1751
+ $document = new Document($html);
1752
+
1753
+ $this->assertTrue($document->first('.element')->hasChildren());
1754
+ $this->assertTrue($document->first('.text')->hasChildren());
1755
+ $this->assertTrue($document->first('.comment')->hasChildren());
1756
+ $this->assertFalse($document->first('.empty')->hasChildren());
1757
+ }
1758
+
1759
+ public function testChildren()
1760
+ {
1761
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
1762
+
1763
+ $document = new Document($html, false);
1764
+
1765
+ $list = $document->first('ul');
1766
+
1767
+ $children = $list->children();
1768
+
1769
+ foreach ($list->getNode()->childNodes as $index => $node) {
1770
+ $this->assertEquals($node, $children[$index]->getNode());
1771
+ }
1772
+
1773
+ // with text nodes
1774
+ $html = '<p>Foo <span>Bar</span> Baz</p>';
1775
+
1776
+ $document = new Document($html, false);
1777
+
1778
+ $paragraph = $document->first('p');
1779
+
1780
+ $children = $paragraph->children();
1781
+
1782
+ foreach ($paragraph->getNode()->childNodes as $index => $node) {
1783
+ $this->assertEquals($node, $children[$index]->getNode());
1784
+ }
1785
+ }
1786
+
1787
+ public function testParentWithoutOwner()
1788
+ {
1789
+ $element = new Element(new \DOMElement('span', 'hello'));
1790
+
1791
+ $this->assertNull($element->parent());
1792
+ }
1793
+
1794
+ public function testRemoveChild()
1795
+ {
1796
+ $html = '<div><span>Foo</span></div>';
1797
+ $document = new Document($html, false);
1798
+
1799
+ $div = $document->first('div');
1800
+ $span = $document->first('span');
1801
+
1802
+ $this->assertEquals($span->getNode(), $div->removeChild($span)->getNode());
1803
+ $this->assertCount(0, $document->find('span'));
1804
+ }
1805
+
1806
+ public function testRemoveChildren()
1807
+ {
1808
+ $html = '<div><span>Foo</span>Bar<!-- Baz --></div>';
1809
+ $document = new Document($html, false);
1810
+
1811
+ $div = $document->first('div');
1812
+ $span = $document->first('span');
1813
+
1814
+ $childNodes = $div->children();
1815
+ $removedNodes = $div->removeChildren();
1816
+
1817
+ foreach ($childNodes as $index => $childNode) {
1818
+ $this->assertEquals($childNode->getNode(), $removedNodes[$index]->getNode());
1819
+ }
1820
+
1821
+ $this->assertCount(0, $document->find('span'));
1822
+ }
1823
+
1824
+ public function testRemove()
1825
+ {
1826
+ $html = '<div><span>Foo</span></div>';
1827
+ $document = new Document($html, false);
1828
+
1829
+ $span = $document->first('span');
1830
+
1831
+ $this->assertEquals($span->getNode(), $span->remove()->getNode());
1832
+ $this->assertCount(0, $document->find('span'));
1833
+ }
1834
+
1835
+ /**
1836
+ * @expectedException \LogicException
1837
+ */
1838
+ public function testRemoveWithoutParentNode()
1839
+ {
1840
+ $element = new Element('div', 'Foo');
1841
+
1842
+ $element->remove();
1843
+ }
1844
+
1845
+ public function testReplace()
1846
+ {
1847
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
1848
+
1849
+ $document = new Document($html, false);
1850
+
1851
+ $first = $document->find('li')[0];
1852
+ $third = $document->find('li')[2];
1853
+
1854
+ $this->assertEquals($first->getNode(), $first->replace($third)->getNode());
1855
+ $this->assertEquals($third->getNode(), $document->find('li')[0]->getNode());
1856
+ $this->assertCount(3, $document->find('li'));
1857
+
1858
+ // replace without cloning
1859
+ $document = new Document($html, false);
1860
+
1861
+ $first = $document->find('li')[0];
1862
+ $third = $document->find('li')[2];
1863
+
1864
+ $this->assertEquals($first->getNode(), $first->replace($third, false)->getNode());
1865
+ $this->assertEquals($third->getNode(), $document->find('li')[0]->getNode());
1866
+ $this->assertCount(2, $document->find('li'));
1867
+ }
1868
+
1869
+ public function testReplaceToNewElement()
1870
+ {
1871
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
1872
+
1873
+ $document = new Document($html, false);
1874
+
1875
+ $first = $document->find('li')[0];
1876
+
1877
+ $newElement = new Element('li', 'Foo');
1878
+
1879
+ $this->assertEquals($first->getNode(), $first->replace($newElement)->getNode());
1880
+ $this->assertEquals('Foo', $document->find('li')[0]->text());
1881
+ $this->assertCount(3, $document->find('li'));
1882
+
1883
+ // replace with new node
1884
+ $html = '<span>Foo <a href="#">Bar</a> Baz</span>';
1885
+
1886
+ $document = new Document($html, false);
1887
+
1888
+ $anchor = $document->first('a');
1889
+
1890
+ $textNode = new \DOMText($anchor->text());
1891
+
1892
+ $anchor->replace($textNode);
1893
+ }
1894
+
1895
+ public function testReplaceWithDifferentDocuments()
1896
+ {
1897
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
1898
+
1899
+ $document = new Document($html, false);
1900
+ $document2 = new Document($html, false);
1901
+
1902
+ $first = $document->find('li')[0];
1903
+ $third = $document2->find('li')[2];
1904
+
1905
+ $first->replace($third);
1906
+ }
1907
+
1908
+ /**
1909
+ * @expectedException \InvalidArgumentException
1910
+ */
1911
+ public function testReplaceWithInvalidArgument()
1912
+ {
1913
+ $html = '<ul><li>One</li><li>Two</li><li>Three</li></ul>';
1914
+
1915
+ $document = new Document($html, false);
1916
+
1917
+ $document->find('li')[0]->replace(null);
1918
+ }
1919
+
1920
+ /**
1921
+ * @expectedException \LogicException
1922
+ */
1923
+ public function testReplaceElementWithoutParentNode()
1924
+ {
1925
+ $element = new Element('div', 'Foo');
1926
+
1927
+ $element->replace(new Element('div', 'Bar'));
1928
+ }
1929
+
1930
+ public function testGetLineNo()
1931
+ {
1932
+ $element = new Element('div');
1933
+
1934
+ $this->assertEquals(0, $element->getLineNo());
1935
+
1936
+ $html = '<ul>
1937
+ <li>One</li>
1938
+ <li>Two</li>
1939
+ <li>Three</li>
1940
+ </ul>';
1941
+
1942
+ $document = new Document($html, false);
1943
+
1944
+ $this->assertEquals(4, $document->find('li')[2]->getLineNo());
1945
+ }
1946
+
1947
+ public function testCloneNode()
1948
+ {
1949
+ $element = new Element('input');
1950
+
1951
+ $cloned = $element->cloneNode(true);
1952
+
1953
+ $this->assertFalse($element->is($cloned));
1954
+ }
1955
+
1956
+ public function testGetNode()
1957
+ {
1958
+ $node = $this->createDomElement('input');
1959
+ $element = new Element($node);
1960
+
1961
+ $this->assertEquals($node, $element->getNode());
1962
+ }
1963
+
1964
+ public function testGetDocument()
1965
+ {
1966
+ $html = $this->loadFixture('posts.html');
1967
+
1968
+ $document = new Document($html, false);
1969
+ $element = $document->createElement('span', 'value');
1970
+
1971
+ $this->assertEquals($document->getDocument(), $element->getDocument()->getDocument());
1972
+ }
1973
+
1974
+ public function testToDocument()
1975
+ {
1976
+ $element = new Element('input');
1977
+
1978
+ $document = $element->toDocument();
1979
+
1980
+ $this->assertInstanceOf('DiDom\Document', $document);
1981
+ $this->assertEquals('UTF-8', $document->getDocument()->encoding);
1982
+
1983
+ $document = $element->toDocument('CP1251');
1984
+
1985
+ $this->assertEquals('CP1251', $document->getDocument()->encoding);
1986
+ }
1987
+
1988
+ public function testSetMagicMethod()
1989
+ {
1990
+ $node = $this->createDomElement('input');
1991
+
1992
+ $element = new Element($node);
1993
+ $element->name = 'username';
1994
+
1995
+ $this->assertEquals('username', $element->getNode()->getAttribute('name'));
1996
+ }
1997
+
1998
+ public function testGetMagicMethod()
1999
+ {
2000
+ $element = new Element('input', null, ['name' => 'username']);
2001
+
2002
+ $this->assertEquals('username', $element->name);
2003
+ }
2004
+
2005
+ public function testIssetMagicMethod()
2006
+ {
2007
+ $node = $this->createDomElement('input');
2008
+ $element = new Element($node);
2009
+
2010
+ $this->assertFalse(isset($element->value));
2011
+
2012
+ $node->setAttribute('value', 'test');
2013
+ $element = new Element($node);
2014
+
2015
+ $this->assertTrue(isset($element->value));
2016
+ }
2017
+
2018
+ public function testUnsetMagicMethod()
2019
+ {
2020
+ $element = new Element('input', null, ['name' => 'username']);
2021
+
2022
+ $this->assertTrue($element->hasAttribute('name'));
2023
+
2024
+ unset($element->name);
2025
+
2026
+ $this->assertFalse($element->hasAttribute('name'));
2027
+ }
2028
+
2029
+ public function testToString()
2030
+ {
2031
+ $element = new Element('span', 'hello');
2032
+
2033
+ $this->assertEquals($element->html(), $element->__toString());
2034
+ }
2035
+
2036
+ /**
2037
+ * @dataProvider findTests
2038
+ */
2039
+ public function testInvoke($html, $selector, $type, $count)
2040
+ {
2041
+ $document = new \DOMDocument();
2042
+ $document->loadHTML($html);
2043
+
2044
+ $node = $document->getElementsByTagName('body')->item(0);
2045
+ $element = new Element($node);
2046
+
2047
+ $elements = $element($selector, $type);
2048
+
2049
+ $this->assertTrue(is_array($elements));
2050
+ $this->assertEquals($count, count($elements));
2051
+
2052
+ foreach ($elements as $element) {
2053
+ $this->assertInstanceOf('DiDom\Element', $element);
2054
+ }
2055
+ }
2056
+ }
vendor/imangazaliev/didom/tests/DiDom/QueryTest.php ADDED
@@ -0,0 +1,425 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Tests\DiDom;
4
+
5
+ use DiDom\Query;
6
+ use Tests\TestCase;
7
+
8
+ class QueryTest extends TestCase
9
+ {
10
+ /**
11
+ * @expectedException \InvalidArgumentException
12
+ * @expectedExceptionMessage DiDom\Query::compile expects parameter 1 to be string, NULL given
13
+ */
14
+ public function testCompileWithNonStringExpression()
15
+ {
16
+ Query::compile(null);
17
+ }
18
+
19
+ /**
20
+ * @expectedException \InvalidArgumentException
21
+ * @expectedExceptionMessage DiDom\Query::compile expects parameter 2 to be string, NULL given
22
+ */
23
+ public function testCompileWithNonStringExpressionType()
24
+ {
25
+ Query::compile('h1', null);
26
+ }
27
+
28
+ /**
29
+ * @expectedException \RuntimeException
30
+ * @expectedExceptionMessage Unknown expression type "foo"
31
+ */
32
+ public function testCompileWithUnknownExpressionType()
33
+ {
34
+ Query::compile('h1', 'foo');
35
+ }
36
+
37
+ /**
38
+ * @dataProvider compileCssTests
39
+ */
40
+ public function testCompileCssSelector($selector, $xpath)
41
+ {
42
+ $this->assertEquals($xpath, Query::compile($selector));
43
+ }
44
+
45
+ /**
46
+ * @dataProvider getSegmentsTests
47
+ */
48
+ public function testGetSegments($selector, $segments)
49
+ {
50
+ $this->assertEquals($segments, Query::getSegments($selector));
51
+ }
52
+
53
+ /**
54
+ * @dataProvider buildXpathTests
55
+ */
56
+ public function testBuildXpath($segments, $xpath)
57
+ {
58
+ $this->assertEquals($xpath, Query::buildXpath($segments));
59
+ }
60
+
61
+ /**
62
+ * @expectedException \InvalidArgumentException
63
+ */
64
+ public function testBuildXpathWithEmptyArray()
65
+ {
66
+ Query::buildXpath([]);
67
+ }
68
+
69
+ /**
70
+ * @expectedException \DiDom\Exceptions\InvalidSelectorException
71
+ * @expectedExceptionMessage The expression must not be empty
72
+ */
73
+ public function testCompileWithEmptyXpathExpression()
74
+ {
75
+ Query::compile('', Query::TYPE_XPATH);
76
+ }
77
+
78
+ /**
79
+ * @expectedException \DiDom\Exceptions\InvalidSelectorException
80
+ * @expectedExceptionMessage The expression must not be empty
81
+ */
82
+ public function testCompileWithEmptyCssExpression()
83
+ {
84
+ Query::compile('', Query::TYPE_CSS);
85
+ }
86
+
87
+ /**
88
+ * @expectedException \DiDom\Exceptions\InvalidSelectorException
89
+ * @expectedExceptionMessage The selector must not be empty
90
+ */
91
+ public function testGetSegmentsWithEmptySelector()
92
+ {
93
+ Query::getSegments('');
94
+ }
95
+
96
+ /**
97
+ * @expectedException \DiDom\Exceptions\InvalidSelectorException
98
+ * @expectedExceptionMessage Invalid selector "input[=foo]": attribute name must not be empty
99
+ */
100
+ public function testEmptyAttributeName()
101
+ {
102
+ Query::compile('input[=foo]');
103
+ }
104
+
105
+ /**
106
+ * @expectedException \DiDom\Exceptions\InvalidSelectorException
107
+ * @expectedExceptionMessage Unknown pseudo-class "unknown-pseudo-class"
108
+ */
109
+ public function testUnknownPseudoClass()
110
+ {
111
+ Query::compile('li:unknown-pseudo-class');
112
+ }
113
+
114
+ /**
115
+ * @dataProvider containsInvalidCaseSensitiveParameterDataProvider
116
+ */
117
+ public function testContainsInvalidCaseSensitiveParameter($caseSensitive)
118
+ {
119
+ $message = sprintf('Parameter 2 of "contains" pseudo-class should be equal true or false, "%s" given', $caseSensitive);
120
+
121
+ $this->setExpectedException('DiDom\Exceptions\InvalidSelectorException', $message);
122
+
123
+ Query::compile("a:contains('Log in', {$caseSensitive})");
124
+ }
125
+
126
+ public function containsInvalidCaseSensitiveParameterDataProvider()
127
+ {
128
+ return [
129
+ ['foo'],
130
+ ['TRUE'],
131
+ ['FALSE'],
132
+ ];
133
+ }
134
+
135
+ /**
136
+ * @expectedException \DiDom\Exceptions\InvalidSelectorException
137
+ * @expectedExceptionMessage nth-child (or nth-last-child) expression must not be empty
138
+ */
139
+ public function testEmptyNthExpression()
140
+ {
141
+ Query::compile('li:nth-child()');
142
+ }
143
+
144
+ /**
145
+ * @expectedException \DiDom\Exceptions\InvalidSelectorException
146
+ * @expectedExceptionMessage Invalid property "::"
147
+ */
148
+ public function testEmptyProperty()
149
+ {
150
+ Query::compile('li::');
151
+ }
152
+
153
+ /**
154
+ * @expectedException \DiDom\Exceptions\InvalidSelectorException
155
+ * @expectedExceptionMessage Unknown property "foo"
156
+ */
157
+ public function testInvalidProperty()
158
+ {
159
+ Query::compile('li::foo');
160
+ }
161
+
162
+ /**
163
+ * @expectedException \DiDom\Exceptions\InvalidSelectorException
164
+ * @expectedExceptionMessage Invalid nth-child expression "foo"
165
+ */
166
+ public function testUnknownNthExpression()
167
+ {
168
+ Query::compile('li:nth-child(foo)');
169
+ }
170
+
171
+ /**
172
+ * @expectedException \DiDom\Exceptions\InvalidSelectorException
173
+ * @expectedExceptionMessage Invalid selector "."
174
+ */
175
+ public function testGetSegmentsWithEmptyClassName()
176
+ {
177
+ Query::getSegments('.');
178
+ }
179
+
180
+ /**
181
+ * @expectedException \DiDom\Exceptions\InvalidSelectorException
182
+ * @expectedExceptionMessage Invalid selector "."
183
+ */
184
+ public function testCompilehWithEmptyClassName()
185
+ {
186
+ Query::compile('span.');
187
+ }
188
+
189
+ public function testCompileXpath()
190
+ {
191
+ $this->assertEquals('//div', Query::compile('//div', Query::TYPE_XPATH));
192
+ }
193
+
194
+ public function testSetCompiledInvalidArgumentType()
195
+ {
196
+ if (PHP_VERSION_ID >= 70000) {
197
+ $this->setExpectedException('TypeError');
198
+ } else {
199
+ $this->setExpectedException('PHPUnit_Framework_Error');
200
+ }
201
+
202
+ Query::setCompiled(null);
203
+ }
204
+
205
+ public function testSetCompiled()
206
+ {
207
+ $xpath = "//*[@id='foo']//*[contains(concat(' ', normalize-space(@class), ' '), ' bar ')]//baz";
208
+ $compiled = ['#foo .bar baz' => $xpath];
209
+
210
+ Query::setCompiled($compiled);
211
+
212
+ $this->assertEquals($compiled, Query::getCompiled());
213
+ }
214
+
215
+ public function testGetCompiled()
216
+ {
217
+ Query::setCompiled([]);
218
+
219
+ $selector = '#foo .bar baz';
220
+ $xpath = '//*[@id="foo"]//*[contains(concat(" ", normalize-space(@class), " "), " bar ")]//baz';
221
+ $compiled = [$selector => $xpath];
222
+
223
+ Query::compile($selector);
224
+
225
+ $this->assertEquals($compiled, Query::getCompiled());
226
+ }
227
+
228
+ public function compileCssTests()
229
+ {
230
+ $compiled = [
231
+ ['a', '//a'],
232
+ ['foo bar baz', '//foo//bar//baz'],
233
+ ['foo > bar > baz', '//foo/bar/baz'],
234
+ ['#foo', '//*[@id="foo"]'],
235
+ ['.bar', '//*[contains(concat(" ", normalize-space(@class), " "), " bar ")]'],
236
+ ['*[foo=bar]', '//*[@foo="bar"]'],
237
+ ['*[foo="bar"]', '//*[@foo="bar"]'],
238
+ ['*[foo=\'bar\']', '//*[@foo="bar"]'],
239
+ ['select[name=category] option[selected=selected]', '//select[@name="category"]//option[@selected="selected"]'],
240
+ ['*[^data-]', '//*[@*[starts-with(name(), "data-")]]'],
241
+ ['*[^data-=foo]', '//*[@*[starts-with(name(), "data-")]="foo"]'],
242
+ ['a[href^=https]', '//a[starts-with(@href, "https")]'],
243
+ ['img[src$=png]', '//img[substring(@src, string-length(@src) - string-length("png") + 1) = "png"]'],
244
+ ['a[href*=example.com]', '//a[contains(@href, "example.com")]'],
245
+ ['script[!src]', '//script[not(@src)]'],
246
+ ['a[href!="http://foo.com/"]', '//a[not(@href="http://foo.com/")]'],
247
+ ['a[foo~="bar"]', '//a[contains(concat(" ", normalize-space(@foo), " "), " bar ")]'],
248
+ ['input, textarea, select', '//input|//textarea|//select'],
249
+ ['input[name="name"], textarea[name="description"], select[name="type"]', '//input[@name="name"]|//textarea[@name="description"]|//select[@name="type"]'],
250
+ ['li:first-child', '//li[position() = 1]'],
251
+ ['li:last-child', '//li[position() = last()]'],
252
+ ['*:not(a[href*="example.com"])', '//*[not(self::a[contains(@href, "example.com")])]'],
253
+ ['ul:empty', '//ul[count(descendant::*) = 0]'],
254
+ ['ul:not-empty', '//ul[count(descendant::*) > 0]'],
255
+ ['li:nth-child(odd)', '//*[(name()="li") and (position() mod 2 = 1 and position() >= 1)]'],
256
+ ['li:nth-child(even)', '//*[(name()="li") and (position() mod 2 = 0 and position() >= 0)]'],
257
+ ['li:nth-child(3)', '//*[(name()="li") and (position() = 3)]'],
258
+ ['li:nth-child(-3)', '//*[(name()="li") and (position() = -3)]'],
259
+ ['li:nth-child(3n)', '//*[(name()="li") and ((position() + 0) mod 3 = 0 and position() >= 0)]'],
260
+ ['li:nth-child(3n+1)', '//*[(name()="li") and ((position() - 1) mod 3 = 0 and position() >= 1)]'],
261
+ ['li:nth-child(3n-1)', '//*[(name()="li") and ((position() + 1) mod 3 = 0 and position() >= 1)]'],
262
+ ['li:nth-child(n+3)', '//*[(name()="li") and ((position() - 3) mod 1 = 0 and position() >= 3)]'],
263
+ ['li:nth-child(n-3)', '//*[(name()="li") and ((position() + 3) mod 1 = 0 and position() >= 3)]'],
264
+ ['li:nth-of-type(odd)', '//li[position() mod 2 = 1 and position() >= 1]'],
265
+ ['li:nth-of-type(even)', '//li[position() mod 2 = 0 and position() >= 0]'],
266
+ ['li:nth-of-type(3)', '//li[position() = 3]'],
267
+ ['li:nth-of-type(-3)', '//li[position() = -3]'],
268
+ ['li:nth-of-type(3n)', '//li[(position() + 0) mod 3 = 0 and position() >= 0]'],
269
+ ['li:nth-of-type(3n+1)', '//li[(position() - 1) mod 3 = 0 and position() >= 1]'],
270
+ ['li:nth-of-type(3n-1)', '//li[(position() + 1) mod 3 = 0 and position() >= 1]'],
271
+ ['li:nth-of-type(n+3)', '//li[(position() - 3) mod 1 = 0 and position() >= 3]'],
272
+ ['li:nth-of-type(n-3)', '//li[(position() + 3) mod 1 = 0 and position() >= 3]'],
273
+ ['ul:has(li.item)', '//ul[.//li[contains(concat(" ", normalize-space(@class), " "), " item ")]]'],
274
+ ['form[name=register]:has(input[name=foo])', '//form[(@name="register") and (.//input[@name="foo"])]'],
275
+ ['ul li a::text', '//ul//li//a/text()'],
276
+ ['ul li a::text()', '//ul//li//a/text()'],
277
+ ['ul li a::attr(href)', '//ul//li//a/@*[name() = "href"]'],
278
+ ['ul li a::attr(href, title)', '//ul//li//a/@*[name() = "href" or name() = "title"]'],
279
+ ['> ul li a', '/ul//li//a'],
280
+ ];
281
+
282
+ $compiled = array_merge($compiled, $this->getContainsPseudoClassTests());
283
+ $compiled = array_merge($compiled, $this->getPropertiesTests());
284
+
285
+ $compiled = array_merge($compiled, [
286
+ ['a[title="foo, bar::baz"]', '//a[@title="foo, bar::baz"]'],
287
+ ]);
288
+
289
+ return $compiled;
290
+ }
291
+
292
+ private function getContainsPseudoClassTests()
293
+ {
294
+ $strToLowerFunction = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower';
295
+
296
+ $containsXpath = [
297
+ // caseSensitive = true, fullMatch = false
298
+ ['li:contains(foo)', '//li[contains(text(), "foo")]'],
299
+ ['li:contains("foo")', '//li[contains(text(), "foo")]'],
300
+ ['li:contains(\'foo\')', '//li[contains(text(), "foo")]'],
301
+
302
+ // caseSensitive = true, fullMatch = false
303
+ ['li:contains(foo, true)', '//li[contains(text(), "foo")]'],
304
+ ['li:contains("foo", true)', '//li[contains(text(), "foo")]'],
305
+ ['li:contains(\'foo\', true)', '//li[contains(text(), "foo")]'],
306
+
307
+ // caseSensitive = true, fullMatch = false
308
+ ['li:contains(foo, true, false)', '//li[contains(text(), "foo")]'],
309
+ ['li:contains("foo", true, false)', '//li[contains(text(), "foo")]'],
310
+ ['li:contains(\'foo\', true, false)', '//li[contains(text(), "foo")]'],
311
+
312
+ // caseSensitive = true, fullMatch = true
313
+ ['li:contains(foo, true, true)', '//li[text() = "foo"]'],
314
+ ['li:contains("foo", true, true)', '//li[text() = "foo"]'],
315
+ ['li:contains(\'foo\', true, true)', '//li[text() = "foo"]'],
316
+
317
+ // caseSensitive = false, fullMatch = false
318
+ ['li:contains(foo, false)', "//li[contains(php:functionString(\"{$strToLowerFunction}\", .), php:functionString(\"{$strToLowerFunction}\", \"foo\"))]"],
319
+ ['li:contains("foo", false)', "//li[contains(php:functionString(\"{$strToLowerFunction}\", .), php:functionString(\"{$strToLowerFunction}\", \"foo\"))]"],
320
+ ['li:contains(\'foo\', false)', "//li[contains(php:functionString(\"{$strToLowerFunction}\", .), php:functionString(\"{$strToLowerFunction}\", \"foo\"))]"],
321
+
322
+ // caseSensitive = false, fullMatch = false
323
+ ['li:contains(foo, false, false)', "//li[contains(php:functionString(\"{$strToLowerFunction}\", .), php:functionString(\"{$strToLowerFunction}\", \"foo\"))]"],
324
+ ['li:contains("foo", false, false)', "//li[contains(php:functionString(\"{$strToLowerFunction}\", .), php:functionString(\"{$strToLowerFunction}\", \"foo\"))]"],
325
+ ['li:contains(\'foo\', false, false)', "//li[contains(php:functionString(\"{$strToLowerFunction}\", .), php:functionString(\"{$strToLowerFunction}\", \"foo\"))]"],
326
+
327
+ // caseSensitive = false, fullMatch = true
328
+ ['li:contains(foo, false, true)', "//li[php:functionString(\"{$strToLowerFunction}\", .) = php:functionString(\"{$strToLowerFunction}\", \"foo\")]"],
329
+ ['li:contains("foo", false, true)', "//li[php:functionString(\"{$strToLowerFunction}\", .) = php:functionString(\"{$strToLowerFunction}\", \"foo\")]"],
330
+ ['li:contains(\'foo\', false, true)', "//li[php:functionString(\"{$strToLowerFunction}\", .) = php:functionString(\"{$strToLowerFunction}\", \"foo\")]"],
331
+ ];
332
+
333
+ return $containsXpath;
334
+ }
335
+
336
+ private function getPropertiesTests()
337
+ {
338
+ return [
339
+ ['a::text', '//a/text()'],
340
+ ['a::text()', '//a/text()'],
341
+ ['a::attr', '//a/@*'],
342
+ ['a::attr()', '//a/@*'],
343
+ ['a::attr(href)', '//a/@*[name() = "href"]'],
344
+ ['a::attr(href,title)', '//a/@*[name() = "href" or name() = "title"]'],
345
+ ['a::attr(href, title)', '//a/@*[name() = "href" or name() = "title"]'],
346
+ ];
347
+ }
348
+
349
+ public function buildXpathTests()
350
+ {
351
+ $xpath = [
352
+ '//a',
353
+ '//*[@id="foo"]',
354
+ '//a[@id="foo"]',
355
+ '//a[contains(concat(" ", normalize-space(@class), " "), " foo ")]',
356
+ '//a[(contains(concat(" ", normalize-space(@class), " "), " foo ")) and (contains(concat(" ", normalize-space(@class), " "), " bar "))]',
357
+ '//a[@href]',
358
+ '//a[@href="http://example.com/"]',
359
+ '//a[(@href="http://example.com/") and (@title="Example Domain")]',
360
+ '//a[(@target="_blank") and (starts-with(@href, "https"))]',
361
+ '//a[substring(@href, string-length(@href) - string-length(".com") + 1) = ".com"]',
362
+ '//a[contains(@href, "example")]',
363
+ '//a[not(@href="http://foo.com/")]',
364
+ '//script[not(@src)]',
365
+ '//li[position() = 1]',
366
+ '//*[(@id="id") and (contains(concat(" ", normalize-space(@class), " "), " foo ")) and (@name="value") and (position() = 1)]',
367
+ ];
368
+
369
+ $segments = [
370
+ ['tag' => 'a'],
371
+ ['id' => 'foo'],
372
+ ['tag' => 'a', 'id' => 'foo'],
373
+ ['tag' => 'a', 'classes' => ['foo']],
374
+ ['tag' => 'a', 'classes' => ['foo', 'bar']],
375
+ ['tag' => 'a', 'attributes' => ['href' => null]],
376
+ ['tag' => 'a', 'attributes' => ['href' => 'http://example.com/']],
377
+ ['tag' => 'a', 'attributes' => ['href' => 'http://example.com/', 'title' => 'Example Domain']],
378
+ ['tag' => 'a', 'attributes' => ['target' => '_blank', 'href^' => 'https']],
379
+ ['tag' => 'a', 'attributes' => ['href$' => '.com']],
380
+ ['tag' => 'a', 'attributes' => ['href*' => 'example']],
381
+ ['tag' => 'a', 'attributes' => ['href!' => 'http://foo.com/']],
382
+ ['tag' => 'script', 'attributes' => ['!src' => null]],
383
+ ['tag' => 'li', 'pseudo' => 'first-child'],
384
+ ['tag' => '*', 'id' => 'id', 'classes' => ['foo'], 'attributes' => ['name' => 'value'], 'pseudo' => 'first-child', 'rel' => '>'],
385
+ ];
386
+
387
+ $parameters = [];
388
+
389
+ foreach ($segments as $index => $segment) {
390
+ $parameters[] = [$segment, $xpath[$index]];
391
+ }
392
+
393
+ return $parameters;
394
+ }
395
+
396
+ public function getSegmentsTests()
397
+ {
398
+ $segments = [
399
+ ['selector' => 'a', 'tag' => 'a'],
400
+ ['selector' => '#foo', 'id' => 'foo'],
401
+ ['selector' => 'a#foo', 'tag' => 'a', 'id' => 'foo'],
402
+ ['selector' => 'a.foo', 'tag' => 'a', 'classes' => ['foo']],
403
+ ['selector' => 'a.foo.bar', 'tag' => 'a', 'classes' => ['foo', 'bar']],
404
+ ['selector' => 'a[href]', 'tag' => 'a', 'attributes' => ['href' => null]],
405
+ ['selector' => 'a[href=http://example.com/]', 'tag' => 'a', 'attributes' => ['href' => 'http://example.com/']],
406
+ ['selector' => 'a[href="http://example.com/"]', 'tag' => 'a', 'attributes' => ['href' => 'http://example.com/']],
407
+ ['selector' => 'a[href=\'http://example.com/\']', 'tag' => 'a', 'attributes' => ['href' => 'http://example.com/']],
408
+ ['selector' => 'a[href=http://example.com/][title=Example Domain]', 'tag' => 'a', 'attributes' => ['href' => 'http://example.com/', 'title' => 'Example Domain']],
409
+ ['selector' => 'a[href=http://example.com/][href=http://example.com/404]', 'tag' => 'a', 'attributes' => ['href' => 'http://example.com/404']],
410
+ ['selector' => 'a[href^=https]', 'tag' => 'a', 'attributes' => ['href^' => 'https']],
411
+ ['selector' => 'li:first-child', 'tag' => 'li', 'pseudo' => 'first-child'],
412
+ ['selector' => 'ul >', 'tag' => 'ul', 'rel' => '>'],
413
+ ['selector' => '#id.foo[name=value]:first-child >', 'id' => 'id', 'classes' => ['foo'], 'attributes' => ['name' => 'value'], 'pseudo' => 'first-child', 'rel' => '>'],
414
+ ['selector' => 'li.bar:nth-child(2n)', 'tag' => 'li', 'classes' => ['bar'], 'pseudo' => 'nth-child', 'expr' => '2n'],
415
+ ];
416
+
417
+ $parameters = [];
418
+
419
+ foreach ($segments as $segment) {
420
+ $parameters[] = [$segment['selector'], $segment];
421
+ }
422
+
423
+ return $parameters;
424
+ }
425
+ }
vendor/imangazaliev/didom/tests/DiDom/SelectorTest.php ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Tests\DiDom;
4
+
5
+ use DiDom\Document;
6
+ use Tests\TestCase;
7
+
8
+ class SelectorTest extends TestCase
9
+ {
10
+ public function testTag()
11
+ {
12
+ $html = '
13
+ <ul id="first">
14
+ <li><a href="#">Item 1</a></li>
15
+ <li><a href="#">Item 2</a></li>
16
+ <li><a href="#">Item 3</a></li>
17
+ </ul>
18
+ <ol id="second">
19
+ <li><a href="#">Item 1</a></li>
20
+ <li><a href="#">Item 2</a></li>
21
+ <li><a href="#">Item 3</a></li>
22
+ </ol>
23
+ ';
24
+
25
+ $document = new Document($html);
26
+
27
+ $expected = ['Item 1', 'Item 2', 'Item 3', 'Item 1', 'Item 2', 'Item 3'];
28
+
29
+ $result = [];
30
+
31
+ foreach ($document->find('li') as $element) {
32
+ $result[] = $element->text();
33
+ }
34
+
35
+ $this->assertEquals($expected, $result);
36
+ }
37
+
38
+ public function testNestedTag()
39
+ {
40
+ $html = '
41
+ <ul id="first">
42
+ <li><a href="#">Item 1</a></li>
43
+ <li><a href="#">Item 2</a></li>
44
+ <li><a href="#">Item 3</a></li>
45
+ </ul>
46
+ <ol id="second">
47
+ <li><a href="#">Item 1</a></li>
48
+ <li><a href="#">Item 2</a></li>
49
+ <li><a href="#">Item 3</a></li>
50
+ </ol>
51
+ ';
52
+
53
+ $document = new Document($html);
54
+
55
+ $expected = ['Item 1', 'Item 2', 'Item 3'];
56
+
57
+ $result = [];
58
+
59
+ foreach ($document->find('ul a') as $element) {
60
+ $result[] = $element->text();
61
+ }
62
+
63
+ $this->assertEquals($expected, $result);
64
+ }
65
+
66
+ public function testDirectChild()
67
+ {
68
+ $html = '
69
+ <div>
70
+ <p><span>Lorem ipsum.</span></p>
71
+ <span>Lorem ipsum.</span>
72
+ </div>
73
+ ';
74
+
75
+ $document = new Document($html);
76
+
77
+ $expected = ['Lorem ipsum.'];
78
+
79
+ $result = [];
80
+
81
+ foreach ($document->find('div > span') as $element) {
82
+ $result[] = $element->text();
83
+ }
84
+
85
+ $this->assertEquals($expected, $result);
86
+ }
87
+
88
+ public function testId()
89
+ {
90
+ $html = '
91
+ <span>Lorem ipsum dolor.</span>
92
+ <span id="second">Tenetur totam, nostrum.</span>
93
+ <span>Iste, doloremque, praesentium.</span>
94
+ ';
95
+
96
+ $document = new Document($html);
97
+
98
+ $expected = ['Tenetur totam, nostrum.'];
99
+
100
+ $result = [];
101
+
102
+ foreach ($document->find('#second') as $element) {
103
+ $result[] = $element->text();
104
+ }
105
+
106
+ $this->assertEquals($expected, $result);
107
+ }
108
+
109
+ public function testClass()
110
+ {
111
+ $html = '
112
+ <span class="odd first">Lorem ipsum dolor.</span>
113
+ <span class="even second">Tenetur totam, nostrum.</span>
114
+ <span class="odd third">Iste, doloremque, praesentium.</span>
115
+ ';
116
+
117
+ $document = new Document($html);
118
+
119
+ $expected = ['Lorem ipsum dolor.', 'Iste, doloremque, praesentium.'];
120
+
121
+ $result = [];
122
+
123
+ foreach ($document->find('.odd') as $element) {
124
+ $result[] = $element->text();
125
+ }
126
+
127
+ $this->assertEquals($expected, $result);
128
+
129
+ $expected = ['Iste, doloremque, praesentium.'];
130
+
131
+ $result = [];
132
+
133
+ foreach ($document->find('.odd.third') as $element) {
134
+ $result[] = $element->text();
135
+ }
136
+
137
+ $this->assertEquals($expected, $result);
138
+ }
139
+
140
+ public function testAttributes()
141
+ {
142
+ $html = '
143
+ <ul class="links">
144
+ <li>
145
+ <a href="https://foo.com" title="Foo" target="_blank">Foo</a>
146
+ <a href="http://bar.com" title="Bar" rel="noreferrer">Bar</a>
147
+ <a href="https://baz.org" title="Baz" rel="nofollow noreferrer">Baz</a>
148
+ <a href="http://qux.org" title="Qux" target="_blank" rel="nofollow">Qux</a>
149
+ </li>
150
+ </ul>
151
+ ';
152
+
153
+ $document = new Document($html);
154
+
155
+ // has attribute
156
+
157
+ $expected = ['Foo', 'Qux'];
158
+
159
+ $result = [];
160
+
161
+ foreach ($document->find('a[target]') as $element) {
162
+ $result[] = $element->text();
163
+ }
164
+
165
+ $this->assertEquals($expected, $result);
166
+
167
+ // has no attribute
168
+
169
+ $expected = ['Bar', 'Baz'];
170
+
171
+ $result = [];
172
+
173
+ foreach ($document->find('a[!target]') as $element) {
174
+ $result[] = $element->text();
175
+ }
176
+
177
+ $this->assertEquals($expected, $result);
178
+
179
+ // equals
180
+
181
+ $expected = ['Baz'];
182
+
183
+ $result = [];
184
+
185
+ foreach ($document->find('a[href="https://baz.org"]') as $element) {
186
+ $result[] = $element->text();
187
+ }
188
+
189
+ $this->assertEquals($expected, $result);
190
+
191
+ // not equals
192
+
193
+ $expected = ['Foo', 'Bar', 'Qux'];
194
+
195
+ $result = [];
196
+
197
+ foreach ($document->find('a[href!="https://baz.org"]') as $element) {
198
+ $result[] = $element->text();
199
+ }
200
+
201
+ $this->assertEquals($expected, $result);
202
+
203
+ // starts with
204
+
205
+ $expected = ['Foo', 'Baz'];
206
+
207
+ $result = [];
208
+
209
+ foreach ($document->find('a[href^="https"]') as $element) {
210
+ $result[] = $element->text();
211
+ }
212
+
213
+ $this->assertEquals($expected, $result);
214
+
215
+ // ends with
216
+
217
+ $expected = ['Baz', 'Qux'];
218
+
219
+ $result = [];
220
+
221
+ foreach ($document->find('a[href$="org"]') as $element) {
222
+ $result[] = $element->text();
223
+ }
224
+
225
+ $this->assertEquals($expected, $result);
226
+
227
+ // contains word
228
+
229
+ $expected = ['Bar', 'Baz'];
230
+
231
+ $result = [];
232
+
233
+ foreach ($document->find('a[rel~="noreferrer"]') as $element) {
234
+ $result[] = $element->text();
235
+ }
236
+
237
+ $this->assertEquals($expected, $result);
238
+ $this->assertEquals([], $document->find('a[rel~="noref"]'));
239
+
240
+ // contains substring
241
+
242
+ $expected = ['Bar', 'Baz'];
243
+
244
+ $result = [];
245
+
246
+ foreach ($document->find('a[href*="ba"]') as $element) {
247
+ $result[] = $element->text();
248
+ }
249
+
250
+ $this->assertEquals($expected, $result);
251
+
252
+ // multiple attribute conditions
253
+
254
+ $expected = ['Qux'];
255
+
256
+ $result = [];
257
+
258
+ foreach ($document->find('a[target="_blank"][rel="nofollow"]') as $element) {
259
+ $result[] = $element->text();
260
+ }
261
+
262
+ $this->assertEquals($expected, $result);
263
+ }
264
+
265
+ /**
266
+ * @param $selector
267
+ * @param $expectedResult
268
+ *
269
+ * @dataProvider containsPseudoClassTests
270
+ */
271
+ public function testContainsPseudoClass($selector, $expectedResult)
272
+ {
273
+ $html = '
274
+ <ul class="links">
275
+ <li>
276
+ <a href="https://foo.com" title="Foo" target="_blank">Foo</a>
277
+ <a href="http://bar.com" title="Bar" rel="noreferrer">Bar</a>
278
+ <a href="https://baz.org" title="Baz" rel="nofollow noreferrer">Baz</a>
279
+ <a href="http://qux.org" title="Qux" target="_blank" rel="nofollow">Qux</a>
280
+ <a href="https://foobar.com" title="FooBar" target="_blank">FooBar</a>
281
+ </li>
282
+ </ul>
283
+ ';
284
+
285
+ $document = new Document($html);
286
+
287
+ $result = [];
288
+
289
+ foreach ($document->find($selector) as $element) {
290
+ $result[] = $element->text();
291
+ }
292
+
293
+ $this->assertEquals($expectedResult, $result);
294
+ }
295
+
296
+ public function containsPseudoClassTests()
297
+ {
298
+ return [
299
+ ['a:contains(Baz)', ['Baz']],
300
+ ['a:contains(a)', ['Bar', 'Baz', 'FooBar']],
301
+ ['a:contains(Bar)', ['Bar', 'FooBar']],
302
+ ['a:contains(Bar, true, true)', ['Bar']],
303
+ ['a:contains(bar)', []],
304
+ ['a:contains(bar, false)', ['Bar', 'FooBar']],
305
+ ['a:contains(bar, false, true)', ['Bar']],
306
+ ];
307
+ }
308
+
309
+ public function testUnicodeSupport()
310
+ {
311
+ $html = '
312
+ <ul class="links">
313
+ <li>
314
+ <a href="http://foo.com" title="Foo">Foo</a>
315
+ <a href="http://example.com" title="Пример">Example</a>
316
+ <a href="http://bar.com" title="Foo">Bar</a>
317
+ <a href="http://example.ru" title="Example">Пример</a>
318
+ </li>
319
+ </ul>
320
+ ';
321
+
322
+ $document = new Document($html);
323
+
324
+ $this->assertEquals('Example', $document->first('a[title=Пример]')->text());
325
+ $this->assertEquals('Example', $document->first('a:contains(Пример)')->attr('title'));
326
+ }
327
+ }
vendor/imangazaliev/didom/tests/DiDom/StyleAttributeTest.php ADDED
@@ -0,0 +1,515 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Tests\DiDom;
4
+
5
+ use DiDom\Element;
6
+ use DiDom\StyleAttribute;
7
+ use Tests\TestCase;
8
+
9
+ class StyleAttributeTest extends TestCase
10
+ {
11
+ /**
12
+ * @expectedException \InvalidArgumentException
13
+ * @expectedExceptionMessage The element should contain DOMElement node
14
+ */
15
+ public function testConstructorWithTextNode()
16
+ {
17
+ $element = new Element(new \DOMText('foo'));
18
+
19
+ new StyleAttribute($element);
20
+ }
21
+
22
+ /**
23
+ * @expectedException \InvalidArgumentException
24
+ * @expectedExceptionMessage The element should contain DOMElement node
25
+ */
26
+ public function testConstructorWithCommentNode()
27
+ {
28
+ $element = new Element(new \DOMComment('foo'));
29
+
30
+ new StyleAttribute($element);
31
+ }
32
+
33
+ /**
34
+ * @expectedException \InvalidArgumentException
35
+ * @expectedExceptionMessage DiDom\StyleAttribute::setProperty expects parameter 1 to be string, NULL given
36
+ */
37
+ public function testSetPropertyWithInvalidPropertyName()
38
+ {
39
+ $element = new Element('div', null, [
40
+ 'style' => 'color: blue; border: 1px solid black',
41
+ ]);
42
+
43
+ $styleAttribute = new StyleAttribute($element);
44
+
45
+ $styleAttribute->setProperty(null, '16px');
46
+ }
47
+
48
+ /**
49
+ * @expectedException \InvalidArgumentException
50
+ * @expectedExceptionMessage DiDom\StyleAttribute::setProperty expects parameter 2 to be string, NULL given
51
+ */
52
+ public function testSetPropertyWithInvalidPropertyValue()
53
+ {
54
+ $element = new Element('div', null, [
55
+ 'style' => 'color: blue; border: 1px solid black',
56
+ ]);
57
+
58
+ $styleAttribute = new StyleAttribute($element);
59
+
60
+ $styleAttribute->setProperty('font-size', null);
61
+ }
62
+
63
+ public function testSetProperty()
64
+ {
65
+ $element = new Element('div', null, [
66
+ 'style' => 'color: blue; border: 1px solid black',
67
+ ]);
68
+
69
+ $styleAttribute = new StyleAttribute($element);
70
+
71
+ $this->assertEquals('color: blue; border: 1px solid black', $element->getAttribute('style'));
72
+
73
+ $styleAttribute->setProperty('font-size', '16px');
74
+
75
+ $this->assertEquals('color: blue; border: 1px solid black; font-size: 16px', $element->getAttribute('style'));
76
+ }
77
+
78
+ /**
79
+ * @expectedException \InvalidArgumentException
80
+ * @expectedExceptionMessage Property name must be a string, integer given
81
+ */
82
+ public function testSetMultiplePropertiesWithInvalidPropertyName()
83
+ {
84
+ $element = new Element('div', null, [
85
+ 'style' => 'color: blue; border: 1px solid black',
86
+ ]);
87
+
88
+ $styleAttribute = new StyleAttribute($element);
89
+
90
+ $styleAttribute->setMultipleProperties([
91
+ 'width' => '50px',
92
+ 'height',
93
+ ]);
94
+ }
95
+
96
+ /**
97
+ * @expectedException \InvalidArgumentException
98
+ * @expectedExceptionMessage Property value must be a string, NULL given
99
+ */
100
+ public function testSetMultiplePropertiesWithInvalidPropertyValue()
101
+ {
102
+ $element = new Element('div', null, [
103
+ 'style' => 'color: blue; border: 1px solid black',
104
+ ]);
105
+
106
+ $styleAttribute = new StyleAttribute($element);
107
+
108
+ $styleAttribute->setMultipleProperties([
109
+ 'width' => '50px',
110
+ 'height' => null,
111
+ ]);
112
+ }
113
+
114
+ public function testSetMultipleProperties()
115
+ {
116
+ $element = new Element('div', null, [
117
+ 'style' => 'color: blue; border: 1px solid black',
118
+ ]);
119
+
120
+ $styleAttribute = new StyleAttribute($element);
121
+
122
+ $this->assertEquals('color: blue; border: 1px solid black', $element->getAttribute('style'));
123
+
124
+ $styleAttribute->setMultipleProperties([
125
+ 'font-size' => '16px',
126
+ 'font-family' => 'Times',
127
+ ]);
128
+
129
+ $this->assertEquals('color: blue; border: 1px solid black; font-size: 16px; font-family: Times', $element->getAttribute('style'));
130
+ }
131
+
132
+ /**
133
+ * @expectedException \InvalidArgumentException
134
+ * @expectedExceptionMessage DiDom\StyleAttribute::getProperty expects parameter 1 to be string, NULL given
135
+ */
136
+ public function testGetPropertyWithInvalidPropertyName()
137
+ {
138
+ $element = new Element('div', null, [
139
+ 'style' => 'color: blue; border: 1px solid black',
140
+ ]);
141
+
142
+ $styleAttribute = new StyleAttribute($element);
143
+
144
+ $styleAttribute->getProperty(null);
145
+ }
146
+
147
+ /**
148
+ * @param string $styleString
149
+ * @param string $propertyName
150
+ * @param string $expectedResult
151
+ *
152
+ * @dataProvider getPropertyDataProvider
153
+ */
154
+ public function testGetProperty($styleString, $propertyName, $expectedResult)
155
+ {
156
+ $element = new Element('div', null, [
157
+ 'style' => $styleString,
158
+ ]);
159
+
160
+ $styleAttribute = new StyleAttribute($element);
161
+
162
+ $this->assertEquals($expectedResult, $styleAttribute->getProperty($propertyName));
163
+ }
164
+
165
+ public function getPropertyDataProvider()
166
+ {
167
+ return [
168
+ [
169
+ 'color: blue; font-size: 16px; border: 1px solid black',
170
+ 'font-size',
171
+ '16px',
172
+ ],
173
+ [
174
+ 'color: blue; font-size: 16px; border: 1px solid black;',
175
+ 'font-size',
176
+ '16px',
177
+ ],
178
+ [
179
+ 'color: blue; font-size: 16px; border: 1px solid black;',
180
+ 'foo',
181
+ null,
182
+ ],
183
+ ];
184
+ }
185
+
186
+ public function testGetPropertyWithDefaultValue()
187
+ {
188
+ $element = new Element('div', null, [
189
+ 'style' => 'color: blue',
190
+ ]);
191
+
192
+ $styleAttribute = new StyleAttribute($element);
193
+
194
+ $this->assertNull($styleAttribute->getProperty('font-size'));
195
+ $this->assertEquals('16px', $styleAttribute->getProperty('font-size', '16px'));
196
+ }
197
+
198
+ /**
199
+ * @expectedException \InvalidArgumentException
200
+ * @expectedExceptionMessage Property name must be a string, NULL given
201
+ */
202
+ public function testGetMultiplePropertiesWithInvalidPropertyName()
203
+ {
204
+ $element = new Element('div', null, [
205
+ 'style' => 'color: blue; border: 1px solid black',
206
+ ]);
207
+
208
+ $styleAttribute = new StyleAttribute($element);
209
+
210
+ $styleAttribute->getMultipleProperties(['color', null]);
211
+ }
212
+
213
+ /**
214
+ * @param string $styleString
215
+ * @param array $propertyNames
216
+ * @param string $expectedResult
217
+ *
218
+ * @dataProvider getMultiplePropertiesDataProvider
219
+ */
220
+ public function testGetMultipleProperties($styleString, $propertyNames, $expectedResult)
221
+ {
222
+ $element = new Element('div', null, [
223
+ 'style' => $styleString,
224
+ ]);
225
+
226
+ $styleAttribute = new StyleAttribute($element);
227
+
228
+ $this->assertEquals($expectedResult, $styleAttribute->getMultipleProperties($propertyNames));
229
+ }
230
+
231
+ public function getMultiplePropertiesDataProvider()
232
+ {
233
+ return [
234
+ [
235
+ 'color: blue; font-size: 16px; font-family: Times; border: 1px solid black',
236
+ ['font-size'],
237
+ [
238
+ 'font-size' => '16px',
239
+ ],
240
+ ],
241
+ [
242
+ 'color: blue; font-size: 16px; font-family: Times; border: 1px solid black',
243
+ ['font-size', 'border'],
244
+ [
245
+ 'font-size' => '16px',
246
+ 'border' => '1px solid black',
247
+ ],
248
+ ],
249
+ [
250
+ 'color: blue; font-size: 16px; font-family: Times; border: 1px solid black',
251
+ ['font-size', 'border', 'width'],
252
+ [
253
+ 'font-size' => '16px',
254
+ 'border' => '1px solid black',
255
+ ],
256
+ ],
257
+ ];
258
+ }
259
+
260
+ /**
261
+ * @param string $styleString
262
+ * @param string $expectedResult
263
+ *
264
+ * @dataProvider getAllPropertiesDataProvider
265
+ */
266
+ public function testGetAllProperties($styleString, $expectedResult)
267
+ {
268
+ $element = new Element('div', null, [
269
+ 'style' => $styleString,
270
+ ]);
271
+
272
+ $styleAttribute = new StyleAttribute($element);
273
+
274
+ $this->assertEquals($expectedResult, $styleAttribute->getAllProperties());
275
+ }
276
+
277
+ public function getAllPropertiesDataProvider()
278
+ {
279
+ return [
280
+ [
281
+ '',
282
+ [],
283
+ ],
284
+ [
285
+ 'color: blue; font-size: 16px; border: 1px solid black',
286
+ [
287
+ 'color' => 'blue',
288
+ 'font-size' => '16px',
289
+ 'border' => '1px solid black',
290
+ ],
291
+ ],
292
+ [
293
+ 'color: blue; font-size: 16px; border: 1px solid black',
294
+ [
295
+ 'color' => 'blue',
296
+ 'font-size' => '16px',
297
+ 'border' => '1px solid black',
298
+ ],
299
+ ],
300
+ ];
301
+ }
302
+
303
+ public function testGetAllPropertiesAfterEmptyStyleAttribute()
304
+ {
305
+ $element = new Element('div', null, [
306
+ 'style' => 'color: blue',
307
+ ]);
308
+
309
+ $styleAttribute = new StyleAttribute($element);
310
+
311
+ $this->assertEquals(['color' => 'blue'], $styleAttribute->getAllProperties());
312
+
313
+ $element->setAttribute('style', '');
314
+
315
+ $this->assertEquals([], $styleAttribute->getAllProperties());
316
+ }
317
+
318
+ /**
319
+ * @expectedException \InvalidArgumentException
320
+ * @expectedExceptionMessage DiDom\StyleAttribute::hasProperty expects parameter 1 to be string, NULL given
321
+ */
322
+ public function testHasPropertyWithInvalidPropertyName()
323
+ {
324
+ $element = new Element('div', null, [
325
+ 'style' => 'color: blue; border: 1px solid black',
326
+ ]);
327
+
328
+ $styleAttribute = new StyleAttribute($element);
329
+
330
+ $styleAttribute->hasProperty(null);
331
+ }
332
+
333
+ public function testHasProperty()
334
+ {
335
+ $element = new Element('div', null, [
336
+ 'style' => 'color: blue; border: 1px solid black',
337
+ ]);
338
+
339
+ $styleAttribute = new StyleAttribute($element);
340
+
341
+ $this->assertTrue($styleAttribute->hasProperty('color'));
342
+ $this->assertFalse($styleAttribute->hasProperty('width'));
343
+ }
344
+
345
+ /**
346
+ * @expectedException \InvalidArgumentException
347
+ * @expectedExceptionMessage DiDom\StyleAttribute::removeProperty expects parameter 1 to be string, NULL given
348
+ */
349
+ public function testRemovePropertyWithInvalidPropertyName()
350
+ {
351
+ $element = new Element('div', null, [
352
+ 'style' => 'color: blue; border: 1px solid black',
353
+ ]);
354
+
355
+ $styleAttribute = new StyleAttribute($element);
356
+
357
+ $styleAttribute->removeProperty(null);
358
+ }
359
+
360
+ public function testRemoveProperty()
361
+ {
362
+ $styleString = 'color: blue; font-size: 16px; border: 1px solid black';
363
+
364
+ $element = new Element('span', 'foo', [
365
+ 'style' => $styleString,
366
+ ]);
367
+
368
+ $styleAttribute = new StyleAttribute($element);
369
+
370
+ $this->assertEquals($styleString, $element->getAttribute('style'));
371
+
372
+ $styleAttribute->removeProperty('font-size');
373
+
374
+ $this->assertEquals('color: blue; border: 1px solid black', $element->getAttribute('style'));
375
+ }
376
+
377
+ /**
378
+ * @expectedException \InvalidArgumentException
379
+ * @expectedExceptionMessage Property name must be a string, NULL given
380
+ */
381
+ public function testRemoveMultiplePropertiesWithInvalidPropertyName()
382
+ {
383
+ $element = new Element('div', null, [
384
+ 'style' => 'color: blue; border: 1px solid black',
385
+ ]);
386
+
387
+ $styleAttribute = new StyleAttribute($element);
388
+
389
+ $styleAttribute->removeMultipleProperties(['color', null]);
390
+ }
391
+
392
+ /**
393
+ * @param string $styleString
394
+ * @param array $propertyNames
395
+ * @param string $expectedResult
396
+ *
397
+ * @dataProvider removeMultiplePropertiesDataProvider
398
+ */
399
+ public function testRemoveMultipleProperties($styleString, $propertyNames, $expectedResult)
400
+ {
401
+ $element = new Element('div', null, [
402
+ 'style' => $styleString,
403
+ ]);
404
+
405
+ $styleAttribute = new StyleAttribute($element);
406
+
407
+ $this->assertEquals($styleString, $element->getAttribute('style'));
408
+
409
+ $styleAttribute->removeMultipleProperties($propertyNames);
410
+
411
+ $this->assertEquals($expectedResult, $element->getAttribute('style'));
412
+ }
413
+
414
+ public function removeMultiplePropertiesDataProvider()
415
+ {
416
+ return [
417
+ [
418
+ 'color: blue; font-size: 16px; font-family: Times; border: 1px solid black',
419
+ [
420
+ 'font-size',
421
+ ],
422
+ 'color: blue; font-family: Times; border: 1px solid black',
423
+ ],
424
+ [
425
+ 'color: blue; font-size: 16px; font-family: Times; border: 1px solid black',
426
+ [
427
+ 'font-size', 'border',
428
+ ],
429
+ 'color: blue; font-family: Times',
430
+ ],
431
+ [
432
+ 'color: blue; font-size: 16px; font-family: Times; border: 1px solid black',
433
+ [
434
+ 'font-size', 'border', 'width',
435
+ ],
436
+ 'color: blue; font-family: Times',
437
+ ],
438
+ ];
439
+ }
440
+
441
+ /**
442
+ * @expectedException \InvalidArgumentException
443
+ * @expectedExceptionMessage Property name must be a string, NULL given
444
+ */
445
+ public function testRemoveAllPropertiesWithInvalidPropertyName()
446
+ {
447
+ $element = new Element('div', null, [
448
+ 'style' => 'color: blue; border: 1px solid black',
449
+ ]);
450
+
451
+ $styleAttribute = new StyleAttribute($element);
452
+
453
+ $styleAttribute->removeAllProperties(['color', null]);
454
+ }
455
+
456
+ /**
457
+ * @param string $styleString
458
+ * @param array $exclusions
459
+ * @param string $expectedResult
460
+ *
461
+ * @dataProvider removeAllPropertiesDataProvider
462
+ */
463
+ public function testRemoveAllProperties($styleString, $exclusions, $expectedResult)
464
+ {
465
+ $element = new Element('div', null, [
466
+ 'style' => $styleString,
467
+ ]);
468
+
469
+ $styleAttribute = new StyleAttribute($element);
470
+
471
+ $this->assertEquals($styleString, $element->getAttribute('style'));
472
+
473
+ $styleAttribute->removeAllProperties($exclusions);
474
+
475
+ $this->assertEquals($expectedResult, $element->getAttribute('style'));
476
+ }
477
+
478
+ public function removeAllPropertiesDataProvider()
479
+ {
480
+ return [
481
+ [
482
+ 'color: blue; font-size: 16px; font-family: Times; border: 1px solid black',
483
+ [
484
+ 'font-size',
485
+ ],
486
+ 'font-size: 16px',
487
+ ],
488
+ [
489
+ 'color: blue; font-size: 16px; font-family: Times; border: 1px solid black',
490
+ [
491
+ 'font-size', 'border',
492
+ ],
493
+ 'font-size: 16px; border: 1px solid black',
494
+ ],
495
+ [
496
+ 'color: blue; font-size: 16px; font-family: Times; border: 1px solid black',
497
+ [
498
+ 'font-size', 'border', 'width',
499
+ ],
500
+ 'font-size: 16px; border: 1px solid black',
501
+ ],
502
+ ];
503
+ }
504
+
505
+ public function testGetElement()
506
+ {
507
+ $element = new Element('div', null, [
508
+ 'style' => 'color: blue; font-size: 16px',
509
+ ]);
510
+
511
+ $styleAttribute = new StyleAttribute($element);
512
+
513
+ $this->assertSame($element, $styleAttribute->getElement());
514
+ }
515
+ }
vendor/imangazaliev/didom/tests/TestCase.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Tests;
4
+
5
+ use PHPUnit_Framework_TestCase;
6
+ use DOMDocument;
7
+ use Exception;
8
+
9
+ class TestCase extends PHPUnit_Framework_TestCase
10
+ {
11
+ protected function tearDown()
12
+ {
13
+ if (class_exists('Mockery')) {
14
+ \Mockery::close();
15
+ }
16
+ }
17
+
18
+ protected function loadFixture($filename)
19
+ {
20
+ $path = __DIR__.'/fixtures/'.$filename;
21
+
22
+ if (file_exists($path)) {
23
+ return file_get_contents($path);
24
+ }
25
+
26
+ throw new Exception(sprintf('Fixture "%s" does not exist', $filename));
27
+ }
28
+
29
+ protected function createDomElement($name, $value = null, $attributes = [])
30
+ {
31
+ $document = new DOMDocument('1.0', 'UTF-8');
32
+ $node = $document->createElement($name, $value);
33
+
34
+ foreach ($attributes as $name => $value) {
35
+ $node->setAttribute($name, $value);
36
+ }
37
+
38
+ return $node;
39
+ }
40
+ }
vendor/imangazaliev/didom/tests/bootstrap.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ ini_set('error_reporting', E_ALL);
4
+ ini_set('display_errors', 1);
5
+ ini_set('display_startup_errors', 1);
6
+
7
+ require __DIR__.'/../vendor/autoload.php';
vendor/imangazaliev/didom/tests/fixtures/books.xml ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <catalog>
3
+ <book id="bk101">
4
+ <author>Gambardella, Matthew</author>
5
+ <title>XML Developer's Guide</title>
6
+ <genre>Computer</genre>
7
+ <price>44.95</price>
8
+ <publish_date>2000-10-01</publish_date>
9
+ <description>An in-depth look at creating applications
10
+ with XML.</description>
11
+ </book>
12
+ <book id="bk102">
13
+ <author>Ralls, Kim</author>
14
+ <title>Midnight Rain</title>
15
+ <genre>Fantasy</genre>
16
+ <price>5.95</price>
17
+ <publish_date>2000-12-16</publish_date>
18
+ <description>A former architect battles corporate zombies,
19
+ an evil sorceress, and her own childhood to become queen
20
+ of the world.</description>
21
+ </book>
22
+ <book id="bk103">
23
+ <author>Corets, Eva</author>
24
+ <title>Maeve Ascendant</title>
25
+ <genre>Fantasy</genre>
26
+ <price>5.95</price>
27
+ <publish_date>2000-11-17</publish_date>
28
+ <description>After the collapse of a nanotechnology
29
+ society in England, the young survivors lay the
30
+ foundation for a new society.</description>
31
+ </book>
32
+ <book id="bk104">
33
+ <author>Corets, Eva</author>
34
+ <title>Oberon's Legacy</title>
35
+ <genre>Fantasy</genre>
36
+ <price>5.95</price>
37
+ <publish_date>2001-03-10</publish_date>
38
+ <description>In post-apocalypse England, the mysterious
39
+ agent known only as Oberon helps to create a new life
40
+ for the inhabitants of London. Sequel to Maeve
41
+ Ascendant.</description>
42
+ </book>
43
+ <book id="bk105">
44
+ <author>Corets, Eva</author>
45
+ <title>The Sundered Grail</title>
46
+ <genre>Fantasy</genre>
47
+ <price>5.95</price>
48
+ <publish_date>2001-09-10</publish_date>
49
+ <description>The two daughters of Maeve, half-sisters,
50
+ battle one another for control of England. Sequel to
51
+ Oberon's Legacy.</description>
52
+ </book>
53
+ <book id="bk106">
54
+ <author>Randall, Cynthia</author>
55
+ <title>Lover Birds</title>
56
+ <genre>Romance</genre>
57
+ <price>4.95</price>
58
+ <publish_date>2000-09-02</publish_date>
59
+ <description>When Carla meets Paul at an ornithology
60
+ conference, tempers fly as feathers get ruffled.</description>
61
+ </book>
62
+ <book id="bk107">
63
+ <author>Thurman, Paula</author>
64
+ <title>Splish Splash</title>
65
+ <genre>Romance</genre>
66
+ <price>4.95</price>
67
+ <publish_date>2000-11-02</publish_date>
68
+ <description>A deep sea diver finds true love twenty
69
+ thousand leagues beneath the sea.</description>
70
+ </book>
71
+ <book id="bk108">
72
+ <author>Knorr, Stefan</author>
73
+ <title>Creepy Crawlies</title>
74
+ <genre>Horror</genre>
75
+ <price>4.95</price>
76
+ <publish_date>2000-12-06</publish_date>
77
+ <description>An anthology of horror stories about roaches,
78
+ centipedes, scorpions and other insects.</description>
79
+ </book>
80
+ <book id="bk109">
81
+ <author>Kress, Peter</author>
82
+ <title>Paradox Lost</title>
83
+ <genre>Science Fiction</genre>
84
+ <price>6.95</price>
85
+ <publish_date>2000-11-02</publish_date>
86
+ <description>After an inadvertant trip through a Heisenberg
87
+ Uncertainty Device, James Salway discovers the problems
88
+ of being quantum.</description>
89
+ </book>
90
+ <book id="bk110">
91
+ <author>O'Brien, Tim</author>
92
+ <title>Microsoft .NET: The Programming Bible</title>
93
+ <genre>Computer</genre>
94
+ <price>36.95</price>
95
+ <publish_date>2000-12-09</publish_date>
96
+ <description>Microsoft's .NET initiative is explored in
97
+ detail in this deep programmer's reference.</description>
98
+ </book>
99
+ <book id="bk111">
100
+ <author>O'Brien, Tim</author>
101
+ <title>MSXML3: A Comprehensive Guide</title>
102
+ <genre>Computer</genre>
103
+ <price>36.95</price>
104
+ <publish_date>2000-12-01</publish_date>
105
+ <description>The Microsoft MSXML3 parser is covered in
106
+ detail, with attention to XML DOM interfaces, XSLT processing,
107
+ SAX and more.</description>
108
+ </book>
109
+ <book id="bk112">
110
+ <author>Galos, Mike</author>
111
+ <title>Visual Studio 7: A Comprehensive Guide</title>
112
+ <genre>Computer</genre>
113
+ <price>49.95</price>
114
+ <publish_date>2001-04-16</publish_date>
115
+ <description>Microsoft Visual Studio 7 is explored in depth,
116
+ looking at how Visual Basic, Visual C++, C#, and ASP+ are
117
+ integrated into a comprehensive development
118
+ environment.</description>
119
+ </book>
120
+ </catalog>
vendor/imangazaliev/didom/tests/fixtures/menu.html ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Document</title>
6
+ </head>
7
+ <body>
8
+ <ul>
9
+ <li><a href="http://example.com">Link 1</a></li>
10
+ <li><a href="http://example.com">Link 2</a></li>
11
+ <li><a href="http://example.com">Link 3</a></li>
12
+ </ul>
13
+ </body>
14
+ </html>
vendor/imangazaliev/didom/tests/fixtures/posts.html ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Posts</title>
6
+ </head>
7
+ <body>
8
+ <h1 class="title">Posts</h1>
9
+
10
+ <div class="posts">
11
+ <div class="post">
12
+ <h2 class="title">Lorem ipsum dolor sit amet.</h2>
13
+ <p class="body">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Inventore autem, in accusamus doloremque temporibus dolorem.</p>
14
+ </div>
15
+ <div class="post">
16
+ <h2 class="title">Distinctio nisi ab in facere.</h2>
17
+ <p class="body">Possimus adipisci atque voluptate non, voluptatum quam, saepe architecto repellat eum quaerat sed nam, quos.</p>
18
+ </div>
19
+ <div class="post">
20
+ <h2 class="title">Consequatur eligendi praesentium voluptatem incidunt.</h2>
21
+ <p class="body">Ad magnam optio maxime cupiditate eos eum. Perferendis voluptatum atque et nobis, facilis iusto! Ipsam.</p>
22
+ </div>
23
+ </div>
24
+ </body>
25
+ </html>