Passed
Branch isEqualNode (8a5aae)
by Josh
02:01
created

NodeComparator::isEqualNode()   D

Complexity

Conditions 30
Paths 17

Size

Total Lines 50
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 27
c 2
b 0
f 0
dl 0
loc 50
rs 4.1666
cc 30
nc 17
nop 2

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
/**
4
* @package   s9e\SweetDOM
5
* @copyright Copyright (c) The s9e authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\SweetDOM;
9
10
use DOMAttr;
11
use DOMCharacterData;
12
use DOMDocument;
13
use DOMDocumentFragment;
14
use DOMDocumentType;
15
use DOMElement;
16
use DOMEntity;
17
use DOMEntityReference;
18
use DOMNode;
19
use DOMNodeList;
20
use DOMNotation;
21
use DOMProcessingInstruction;
22
use DOMXPath;
23
24
class NodeComparator
25
{
26
	// https://dom.spec.whatwg.org/#concept-node-equals
27
	// https://github.com/php/php-src/blob/master/ext/dom/node.c
28
	public static function isEqualNode(?DOMNode $node, ?DOMNode $otherNode): bool
29
	{
30
		if (!isset($node, $otherNode) || $node->nodeType !== $otherNode->nodeType)
31
		{
32
			return false;
33
		}
34
		if ($node instanceof DOMElement && $otherNode instanceof DOMElement)
35
		{
36
			return static::isEqualElementNode($node, $otherNode);
37
		}
38
		if ($node instanceof DOMCharacterData && $otherNode instanceof DOMCharacterData)
39
		{
40
			// Covers DOMCdataSection, DOMComment, and DOMText
41
			return $node->data === $otherNode->data;
42
		}
43
		if ($node instanceof DOMProcessingInstruction && $otherNode instanceof DOMProcessingInstruction)
44
		{
45
			return $node->target === $otherNode->target && $node->data === $otherNode->data;
46
		}
47
		if ($node instanceof DOMAttr && $otherNode instanceof DOMAttr)
48
		{
49
			return $node->namespaceURI === $otherNode->namespaceURI
50
			    && $node->localName    === $otherNode->localName
51
			    && $node->value        === $otherNode->value;
52
		}
53
		if (($node instanceof DOMDocument         && $otherNode instanceof DOMDocument)
54
		 || ($node instanceof DOMDocumentFragment && $otherNode instanceof DOMDocumentFragment))
55
		{
56
			return static::isEqualNodeList($node->childNodes, $otherNode->childNodes);
57
		}
58
		if ($node instanceof DOMDocumentType && $otherNode instanceof DOMDocumentType)
59
		{
60
			return $node->name     === $otherNode->name
61
			    && $node->publicId === $otherNode->publicId
62
			    && $node->systemId === $otherNode->systemId;
63
		}
64
		if ($node instanceof DOMEntityReference && $otherNode instanceof DOMEntityReference)
65
		{
66
			return $node->nodeName === $otherNode->nodeName;
67
		}
68
		if (($node instanceof DOMEntity   && $otherNode instanceof DOMEntity)
69
		 || ($node instanceof DOMNotation && $otherNode instanceof DOMNotation))
70
		{
71
			return $node->nodeName === $otherNode->nodeName
72
			    && $node->publicId === $otherNode->publicId
73
			    && $node->systemId === $otherNode->systemId;
74
		}
75
76
		// @codeCoverageIgnoreStart
77
		return $node->isSameNode($otherNode);
78
		// @codeCoverageIgnoreEnd
79
	}
80
81
	/**
82
	* @return array<string, string>
83
	*/
84
	protected static function getNamespaceDeclarations(DOMElement $element): array
85
	{
86
		$namespaces = [];
87
		$xpath      = new DOMXPath($element->ownerDocument);
1 ignored issue
show
Bug introduced by
It seems like $element->ownerDocument can also be of type null; however, parameter $document of DOMXPath::__construct() does only seem to accept DOMDocument, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

87
		$xpath      = new DOMXPath(/** @scrutinizer ignore-type */ $element->ownerDocument);
Loading history...
88
		foreach ($xpath->query('namespace::*', $element) as $node)
89
		{
90
			if ($element->hasAttribute($node->nodeName))
91
			{
92
				$namespaces[$node->nodeName] = $node->nodeValue;
93
			}
94
		}
95
96
		return $namespaces;
97
	}
98
99
	protected static function hasEqualNamespaceDeclarations(DOMElement $element, DOMElement $otherElement): bool
100
	{
101
		return static::getNamespaceDeclarations($element) == static::getNamespaceDeclarations($otherElement);
102
	}
103
104
	protected static function isEqualElementNode(DOMElement $element, DOMElement $otherElement): bool
105
	{
106
		if ($element->namespaceURI       !== $otherElement->namespaceURI
107
		 || $element->nodeName           !== $otherElement->nodeName
108
		 || $element->attributes->length !== $otherElement->attributes->length
109
		 || $element->childNodes->length !== $otherElement->childNodes->length)
110
		{
111
			return false;
112
		}
113
114
		foreach ($element->attributes as $attribute)
115
		{
116
			if ($attribute->value !== $otherElement->attributes->getNamedItem($attribute->name)?->value)
1 ignored issue
show
Bug introduced by
The method getNamedItem() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

116
			if ($attribute->value !== $otherElement->attributes->/** @scrutinizer ignore-call */ getNamedItem($attribute->name)?->value)

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
117
			{
118
				return false;
119
			}
120
		}
121
122
		return static::isEqualNodeList($element->childNodes, $otherElement->childNodes)
123
		    && static::hasEqualNamespaceDeclarations($element, $otherElement);
124
	}
125
126
	protected static function isEqualNodeList(DOMNodeList $list, DOMNodeList $otherList): bool
127
	{
128
		if ($list->length !== $otherList->length)
129
		{
130
			return false;
131
		}
132
		foreach ($list as $i => $node)
133
		{
134
			if (!static::isEqualNode($node, $otherList->item($i)))
135
			{
136
				return false;
137
			}
138
		}
139
140
		return true;
141
	}
142
}