Passed
Branch isEqualNode (a7d3cf)
by Josh
01:25
created

NodeComparator::isEqualProcessingInstruction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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