HtmlMatcher   A
last analyzed

Complexity

Total Complexity 12

Size/Duplication

Total Lines 106
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 95.65%

Importance

Changes 0
Metric Value
dl 0
loc 106
ccs 44
cts 46
cp 0.9565
rs 10
c 0
b 0
f 0
wmc 12
lcom 1
cbo 3

6 Methods

Rating   Name   Duplication   Size   Complexity  
A htmlPiece() 0 3 1
A __construct() 0 3 1
A describeTo() 0 6 2
B matchesWithDiagnosticDescription() 0 45 6
A isUnknownTagError() 0 3 1
A escapeScriptTagContents() 0 5 1
1
<?php
2
3
namespace WMDE\HamcrestHtml;
4
5
use Hamcrest\Description;
6
use Hamcrest\DiagnosingMatcher;
7
use Hamcrest\Matcher;
8
9
class HtmlMatcher extends DiagnosingMatcher {
10
11
	/**
12
	 * @link http://www.xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
13
	 * @link https://github.com/Chronic-Dev/libxml2/blob/683f296a905710ff285c28b8644ef3a3d8be9486/include/libxml/xmlerror.h#L257
14
	 */
15
	const XML_UNKNOWN_TAG_ERROR_CODE = 801;
16
17
	/**
18
	 * @var Matcher
19
	 */
20
	private $elementMatcher;
21
22
	/**
23
	 * @param Matcher $elementMatcher
24
	 *
25
	 * @return self
26
	 */
27 66
	public static function htmlPiece( Matcher $elementMatcher = null ) {
28 66
		return new static( $elementMatcher );
29
	}
30
31 66
	private function __construct( Matcher $elementMatcher = null ) {
32 66
		$this->elementMatcher = $elementMatcher;
33 66
	}
34
35 15
	public function describeTo( Description $description ) {
36 15
		$description->appendText( 'valid html piece ' );
37 15
		if ( $this->elementMatcher ) {
38 14
			$description->appendDescriptionOf( $this->elementMatcher );
39 14
		}
40 15
	}
41
42
	/**
43
	 * @param string $html
44
	 * @param Description $mismatchDescription
45
	 *
46
	 * @return bool
47
	 */
48 66
	protected function matchesWithDiagnosticDescription( $html, Description $mismatchDescription ) {
49 66
		$internalErrors = libxml_use_internal_errors( true );
50 66
		libxml_clear_errors();
51 66
		$document = new \DOMDocument();
52
53 66
		$html = $this->escapeScriptTagContents( $html );
54
55
		// phpcs:ignore Generic.PHP.NoSilencedErrors
56 66
		if ( [email protected]$document->loadHTML( mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' ) ) ) {
57
			$mismatchDescription->appendText( 'there was some parsing error' );
58
			return false;
59
		}
60
61 66
		$errors = libxml_get_errors();
62 66
		libxml_clear_errors();
63 66
		libxml_use_internal_errors( $internalErrors );
64
65 66
		$result = true;
66
		/** @var \LibXMLError $error */
67 66
		foreach ( $errors as $error ) {
68 32
			if ( $this->isUnknownTagError( $error ) ) {
69 31
				continue;
70
			}
71
72 1
			$mismatchDescription->appendText( 'there was parsing error: ' )
73 1
				->appendText( trim( $error->message ) )
74 1
				->appendText( ' on line ' )
75 1
				->appendText( $error->line );
76 1
			$result = false;
77 66
		}
78
79 66
		if ( !$result ) {
80 1
			return false;
81
		}
82 65
		$mismatchDescription->appendText( 'valid html piece ' );
83
84 65
		if ( $this->elementMatcher ) {
85 30
			$result = $this->elementMatcher->matches( $document );
86 30
			$this->elementMatcher->describeMismatch( $document, $mismatchDescription );
87 30
		}
88
89 65
		$mismatchDescription->appendText( "\nActual html:\n" )->appendText( $html );
90
91 65
		return $result;
92
	}
93
94
	/**
95
	 * @param \LibXMLError $error
96
	 *
97
	 * @return bool
98
	 */
99 32
	private function isUnknownTagError( \LibXMLError $error ) {
100 32
		return $error->code === self::XML_UNKNOWN_TAG_ERROR_CODE;
101
	}
102
103
	/**
104
	 * @param string $html
105
	 *
106
	 * @return string HTML
107
	 */
108
	private function escapeScriptTagContents( $html ) {
109 66
		return preg_replace_callback( '#(<script.*>)(.*)(</script>)#isU', function ( $matches ) {
110 3
			return $matches[1] . str_replace( '</', '<\/', $matches[2] ) . $matches[3];
111 66
		}, $html );
112
	}
113
114
}
115