Passed
Branch isEqualNode (9db26a)
by Josh
01:28
created

NodeComparator::isEqualEntityNotation()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 5
rs 10
cc 3
nc 3
nop 2
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
		$classes = [
35
			'DOMElement'               => 'isEqualElement',
36
			'DOMCharacterData'         => 'isEqualCharacterData',
37
			'DOMProcessingInstruction' => 'isEqualProcessingInstruction',
38
			'DOMAttr'                  => 'isEqualAttr',
39
			'DOMDocument'              => 'isEqualDocumentFragment',
40
			'DOMDocumentFragment'      => 'isEqualDocumentFragment',
41
			'DOMDocumentType'          => 'isEqualDocumentType',
42
			'DOMEntityReference'       => 'isEqualEntityReference',
43
			'DOMEntity'                => 'isEqualEntityNotation',
44
			'DOMNotation'              => 'isEqualEntityNotation'
45
		];
46
		foreach ($classes as $className => $methodName)
47
		{
48
			if ($node instanceof $className && $otherNode instanceof $className)
49
			{
50
				return static::$methodName($node, $otherNode);
51
			}
52
		}
53
54
		// @codeCoverageIgnoreStart
55
		return $node->isSameNode($otherNode);
56
		// @codeCoverageIgnoreEnd
57
	}
58
59
	/**
60
	* @return array<string, string>
61
	*/
62
	protected static function getNamespaceDeclarations(DOMElement $element): array
63
	{
64
		$namespaces = [];
65
		$xpath      = new DOMXPath($element->ownerDocument);
66
		foreach ($xpath->query('namespace::*', $element) as $node)
67
		{
68
			if ($element->hasAttribute($node->nodeName))
69
			{
70
				$namespaces[$node->nodeName] = $node->nodeValue;
71
			}
72
		}
73
74
		return $namespaces;
75
	}
76
77
	protected static function hasEqualNamespaceDeclarations(DOMElement $element, DOMElement $otherElement): bool
78
	{
79
		return static::getNamespaceDeclarations($element) == static::getNamespaceDeclarations($otherElement);
80
	}
81
82
	protected static function isEqualAttr(DOMAttr $node, DOMAttr $otherNode): bool
83
	{
84
		return $node->namespaceURI === $otherNode->namespaceURI
85
		    && $node->localName    === $otherNode->localName
86
		    && $node->value        === $otherNode->value;
87
	}
88
89
	protected static function isEqualCharacterData(DOMCharacterData $node, DOMCharacterData $otherNode): bool
90
	{
91
		// Covers DOMCdataSection, DOMComment, and DOMText
92
		return $node->data === $otherNode->data;
93
	}
94
95
	protected static function isEqualDocumentFragment(DOMDocument|DOMDocumentFragment $node, DOMDocument|DOMDocumentFragment $otherNode): bool
96
	{
97
		return static::isEqualNodeList($node->childNodes, $otherNode->childNodes);
98
	}
99
100
	protected static function isEqualDocumentType(DOMDocumentType $node, DOMDocumentType $otherNode): bool
101
	{
102
		return $node->name     === $otherNode->name
103
		    && $node->publicId === $otherNode->publicId
104
		    && $node->systemId === $otherNode->systemId;
105
	}
106
107
	protected static function isEqualElement(DOMElement $element, DOMElement $otherElement): bool
108
	{
109
		if ($element->namespaceURI       !== $otherElement->namespaceURI
110
		 || $element->nodeName           !== $otherElement->nodeName
111
		 || $element->attributes->length !== $otherElement->attributes->length
112
		 || $element->childNodes->length !== $otherElement->childNodes->length)
113
		{
114
			return false;
115
		}
116
117
		foreach ($element->attributes as $attribute)
118
		{
119
			if ($attribute->value !== $otherElement->attributes->getNamedItem($attribute->name)?->value)
120
			{
121
				return false;
122
			}
123
		}
124
125
		return static::isEqualNodeList($element->childNodes, $otherElement->childNodes)
126
		    && static::hasEqualNamespaceDeclarations($element, $otherElement);
127
	}
128
129
	protected static function isEqualEntityNotation(DOMEntity|DOMNotation $node, DOMEntity|DOMNotation $otherNode): bool
130
	{
131
		return $node->nodeName === $otherNode->nodeName
132
		    && $node->publicId === $otherNode->publicId
133
		    && $node->systemId === $otherNode->systemId;
134
	}
135
136
	protected static function isEqualEntityReference(DOMEntityReference $node, DOMEntityReference $otherNode): bool
137
	{
138
		return $node->nodeName === $otherNode->nodeName;
139
	}
140
141
	protected static function isEqualNodeList(DOMNodeList $list, DOMNodeList $otherList): bool
142
	{
143
		if ($list->length !== $otherList->length)
144
		{
145
			return false;
146
		}
147
		foreach ($list as $i => $node)
148
		{
149
			if (!static::isEqualNode($node, $otherList->item($i)))
150
			{
151
				return false;
152
			}
153
		}
154
155
		return true;
156
	}
157
158
	protected static function isEqualProcessingInstruction(DOMProcessingInstruction $node, DOMProcessingInstruction $otherNode): bool
159
	{
160
		return $node->target === $otherNode->target && $node->data === $otherNode->data;
161
	}
162
}