Version Description
- First release.
=
Download this release
Release Info
Developer | TigrouMeow |
Plugin | Gallery Custom Links |
Version | 0.0.1 |
Comparing to | |
See all releases |
Version 0.0.1
- composer.json +5 -0
- composer.lock +64 -0
- gallery_custom_links.php +35 -0
- mgcl_core.php +104 -0
- readme.txt +44 -0
- vendor/autoload.php +7 -0
- vendor/composer/ClassLoader.php +445 -0
- vendor/composer/LICENSE +21 -0
- vendor/composer/autoload_classmap.php +9 -0
- vendor/composer/autoload_namespaces.php +9 -0
- vendor/composer/autoload_psr4.php +10 -0
- vendor/composer/autoload_real.php +52 -0
- vendor/composer/autoload_static.php +31 -0
- vendor/composer/installed.json +50 -0
- vendor/imangazaliev/didom/.php_cs +207 -0
- vendor/imangazaliev/didom/.travis.yml +20 -0
- vendor/imangazaliev/didom/CHANGELOG.md +211 -0
- vendor/imangazaliev/didom/LICENSE +19 -0
- vendor/imangazaliev/didom/README-RU.md +833 -0
- vendor/imangazaliev/didom/README.md +603 -0
- vendor/imangazaliev/didom/composer.json +30 -0
- vendor/imangazaliev/didom/phpunit.xml +17 -0
- vendor/imangazaliev/didom/src/DiDom/ClassAttribute.php +269 -0
- vendor/imangazaliev/didom/src/DiDom/Document.php +663 -0
- vendor/imangazaliev/didom/src/DiDom/Element.php +1445 -0
- vendor/imangazaliev/didom/src/DiDom/Encoder.php +58 -0
- vendor/imangazaliev/didom/src/DiDom/Errors.php +40 -0
- vendor/imangazaliev/didom/src/DiDom/Exceptions/InvalidSelectorException.php +10 -0
- vendor/imangazaliev/didom/src/DiDom/Query.php +559 -0
- vendor/imangazaliev/didom/src/DiDom/StyleAttribute.php +324 -0
- vendor/imangazaliev/didom/tests/DiDom/ClassAttributeTest.php +383 -0
- vendor/imangazaliev/didom/tests/DiDom/DocumentTest.php +709 -0
- vendor/imangazaliev/didom/tests/DiDom/ElementTest.php +2056 -0
- vendor/imangazaliev/didom/tests/DiDom/QueryTest.php +425 -0
- vendor/imangazaliev/didom/tests/DiDom/SelectorTest.php +327 -0
- vendor/imangazaliev/didom/tests/DiDom/StyleAttributeTest.php +515 -0
- vendor/imangazaliev/didom/tests/TestCase.php +40 -0
- vendor/imangazaliev/didom/tests/bootstrap.php +7 -0
- vendor/imangazaliev/didom/tests/fixtures/books.xml +120 -0
- vendor/imangazaliev/didom/tests/fixtures/menu.html +14 -0
- 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 |
+
< >
|
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 |
+
< >
|
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 |
+
< >
|
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>
|