Skip to content

Commit

Permalink
Added isEqualNode() polyfill
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshyPHP committed Jan 1, 2024
1 parent aecb43a commit b07ae8f
Show file tree
Hide file tree
Showing 23 changed files with 747 additions and 2 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Overview

s9e\\SweetDOM is a library that extends [PHP's DOM extension](https://www.php.net/manual/en/book.dom.php) with a set of methods designed to simplify and facilitate the manipulation of XSLT 1.0 templates.
s9e\\SweetDOM is a library that extends [PHP's DOM extension](https://www.php.net/manual/en/book.dom.php) to make DOM manipulation easier, with a particular emphasis on XSLT 1.0 templates. It adds syntactic sugar for the most common DOM operations, [improves compatibility](#backward-and-forward-compatibility-with-older-and-future-versions-of-php) across PHP versions, and implements polyfills for some of the newer methods.

[![Code Coverage](https://scrutinizer-ci.com/g/s9e/SweetDOM/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/s9e/SweetDOM/?branch=master)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/s9e/SweetDOM/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/s9e/SweetDOM/?branch=master)
Expand Down Expand Up @@ -173,6 +173,7 @@ In order to improve compatibility with older versions of PHP as well as future v

Polyfills for the following methods are provided for PHP < 8.3:

- `Node::isEqualNode`
- `ParentNode::insertAdjacentElement`
- `ParentNode::insertAdjacentText`
- `ParentNode::replaceChildren`
Expand Down
9 changes: 8 additions & 1 deletion src/Document.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
use DOMXPath;
use RuntimeException;
use const PHP_VERSION;
use function func_get_args, libxml_get_last_error, trim, version_compare;
use function func_get_args, method_exists, libxml_get_last_error, trim, version_compare;

/**
* @method Attr|false createAttribute(string $localName)
Expand Down Expand Up @@ -66,6 +66,13 @@ public function firstOf(string $expression, ?DOMNode $contextNode = null, bool $
return $this->query(...func_get_args())->item(0);
}

public function isEqualNode(?DOMNode $otherNode): bool
{
return method_exists('DOMDocument', 'isEqualNode')
? parent::isEqualNode($otherNode)
: NodeComparator::isEqualNode($this, $otherNode);
}

/**
* Evaluate and return the result of a given XPath query
*/
Expand Down
2 changes: 2 additions & 0 deletions src/ForwardCompatibleNodes/Attr.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
namespace s9e\SweetDOM\ForwardCompatibleNodes;

use s9e\SweetDOM\Attr as ParentClass;
use s9e\SweetDOM\NodeTraits\NodePolyfill;

class Attr extends ParentClass
{
use NodePolyfill;
}
2 changes: 2 additions & 0 deletions src/ForwardCompatibleNodes/CdataSection.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@

use s9e\SweetDOM\CdataSection as ParentClass;
use s9e\SweetDOM\NodeTraits\ChildNodeForwardCompatibility;
use s9e\SweetDOM\NodeTraits\NodePolyfill;

class CdataSection extends ParentClass
{
use ChildNodeForwardCompatibility;
use NodePolyfill;
}
2 changes: 2 additions & 0 deletions src/ForwardCompatibleNodes/Comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@

use s9e\SweetDOM\Comment as ParentClass;
use s9e\SweetDOM\NodeTraits\ChildNodeForwardCompatibility;
use s9e\SweetDOM\NodeTraits\NodePolyfill;

class Comment extends ParentClass
{
use ChildNodeForwardCompatibility;
use NodePolyfill;
}
2 changes: 2 additions & 0 deletions src/ForwardCompatibleNodes/DocumentFragment.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
namespace s9e\SweetDOM\ForwardCompatibleNodes;

use s9e\SweetDOM\DocumentFragment as ParentClass;
use s9e\SweetDOM\NodeTraits\NodePolyfill;
use s9e\SweetDOM\NodeTraits\ParentNodePolyfill;

class DocumentFragment extends ParentClass
{
use NodePolyfill;
use ParentNodePolyfill;
}
2 changes: 2 additions & 0 deletions src/ForwardCompatibleNodes/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@

use s9e\SweetDOM\Element as ParentClass;
use s9e\SweetDOM\NodeTraits\ChildNodeForwardCompatibility;
use s9e\SweetDOM\NodeTraits\NodePolyfill;
use s9e\SweetDOM\NodeTraits\ParentNodePolyfill;

class Element extends ParentClass
{
use ChildNodeForwardCompatibility;
use NodePolyfill;
use ParentNodePolyfill;
}
2 changes: 2 additions & 0 deletions src/ForwardCompatibleNodes/Text.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
namespace s9e\SweetDOM\ForwardCompatibleNodes;

use s9e\SweetDOM\NodeTraits\ChildNodeForwardCompatibility;
use s9e\SweetDOM\NodeTraits\NodePolyfill;
use s9e\SweetDOM\Text as ParentClass;

class Text extends ParentClass
{
use ChildNodeForwardCompatibility;
use NodePolyfill;
}
177 changes: 177 additions & 0 deletions src/NodeComparator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<?php declare(strict_types=1);

/**
* @package s9e\SweetDOM
* @copyright Copyright (c) The s9e authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\SweetDOM;

use DOMAttr;
use DOMCharacterData;
use DOMDocument;
use DOMDocumentFragment;
use DOMDocumentType;
use DOMElement;
use DOMEntity;
use DOMEntityReference;
use DOMNode;
use DOMNodeList;
use DOMNotation;
use DOMProcessingInstruction;
use DOMXPath;
use function substr;

class NodeComparator
{
// https://dom.spec.whatwg.org/#concept-node-equals
// https://github.com/php/php-src/blob/master/ext/dom/node.c
public static function isEqualNode(?DOMNode $node, ?DOMNode $otherNode): bool
{
if (!isset($node, $otherNode) || $node->nodeType !== $otherNode->nodeType)
{
return false;
}
$classes = [
'DOMElement',
'DOMCharacterData',
'DOMProcessingInstruction',
'DOMAttr',
'DOMDocument',
'DOMDocumentFragment',
'DOMDocumentType',
'DOMEntityReference',
'DOMEntity',
'DOMNotation'
];
foreach ($classes as $className)
{
if ($node instanceof $className && $otherNode instanceof $className)
{
$methodName = 'isEqual' . substr($className, 3);

return static::$methodName($node, $otherNode);
}
}

// @codeCoverageIgnoreStart
return $node->isSameNode($otherNode);
// @codeCoverageIgnoreEnd
}

/**
* @return array<string, string>
*/
protected static function getNamespaceDeclarations(DOMElement $element): array
{
$namespaces = [];
$xpath = new DOMXPath($element->ownerDocument);
foreach ($xpath->query('namespace::*', $element) as $node)
{
if ($element->hasAttribute($node->nodeName))
{
$namespaces[$node->nodeName] = $node->nodeValue;
}
}

return $namespaces;
}

protected static function hasEqualNamespaceDeclarations(DOMElement $element, DOMElement $otherElement): bool
{
return static::getNamespaceDeclarations($element) == static::getNamespaceDeclarations($otherElement);
}

protected static function isEqualAttr(DOMAttr $node, DOMAttr $otherNode): bool
{
return $node->namespaceURI === $otherNode->namespaceURI
&& $node->localName === $otherNode->localName
&& $node->value === $otherNode->value;
}

protected static function isEqualCharacterData(DOMCharacterData $node, DOMCharacterData $otherNode): bool
{
// Covers DOMCdataSection, DOMComment, and DOMText
return $node->data === $otherNode->data;
}

protected static function isEqualDocument(DOMDocument $node, DOMDocument $otherNode): bool
{
return static::isEqualNodeList($node->childNodes, $otherNode->childNodes);
}

protected static function isEqualDocumentFragment(DOMDocumentFragment $node, DOMDocumentFragment $otherNode): bool
{
return static::isEqualNodeList($node->childNodes, $otherNode->childNodes);
}

protected static function isEqualDocumentType(DOMDocumentType $node, DOMDocumentType $otherNode): bool
{
return $node->name === $otherNode->name
&& $node->publicId === $otherNode->publicId
&& $node->systemId === $otherNode->systemId;
}

protected static function isEqualElement(DOMElement $element, DOMElement $otherElement): bool
{
if ($element->namespaceURI !== $otherElement->namespaceURI
|| $element->nodeName !== $otherElement->nodeName
|| $element->attributes->length !== $otherElement->attributes->length
|| $element->childNodes->length !== $otherElement->childNodes->length)
{
return false;
}

foreach ($element->attributes as $attribute)
{
if ($attribute->value !== $otherElement->attributes->getNamedItem($attribute->name)?->value)
{
return false;
}
}

return static::isEqualNodeList($element->childNodes, $otherElement->childNodes)
&& static::hasEqualNamespaceDeclarations($element, $otherElement);
}

protected static function isEqualEntity(DOMEntity $node, DOMEntity $otherNode): bool
{
return $node->nodeName === $otherNode->nodeName
&& $node->publicId === $otherNode->publicId
&& $node->systemId === $otherNode->systemId;
}

protected static function isEqualEntityReference(DOMEntityReference $node, DOMEntityReference $otherNode): bool
{
return $node->nodeName === $otherNode->nodeName;
}

protected static function isEqualNodeList(DOMNodeList $list, DOMNodeList $otherList): bool
{
if ($list->length !== $otherList->length)
{
return false;
}
foreach ($list as $i => $node)
{
if (!static::isEqualNode($node, $otherList->item($i)))
{
return false;
}
}

return true;
}

protected static function isEqualNotation(DOMNotation $node, DOMNotation $otherNode): bool
{
return $node->nodeName === $otherNode->nodeName
&& $node->publicId === $otherNode->publicId
&& $node->systemId === $otherNode->systemId;
}

protected static function isEqualProcessingInstruction(DOMProcessingInstruction $node, DOMProcessingInstruction $otherNode): bool
{
return $node->target === $otherNode->target && $node->data === $otherNode->data;
}
}
22 changes: 22 additions & 0 deletions src/NodeTraits/NodePolyfill.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php declare(strict_types=1);

/**
* @package s9e\SweetDOM
* @copyright Copyright (c) The s9e authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\SweetDOM\NodeTraits;

use DOMAttr;
use DOMCharacterData;
use DOMElement;
use DOMNode;
use s9e\SweetDOM\NodeComparator;

trait NodePolyfill
{
public function isEqualNode(?DOMNode $otherNode): bool
{
return NodeComparator::isEqualNode($this, $otherNode);
}
}
2 changes: 2 additions & 0 deletions src/PatchedNodes/Attr.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
namespace s9e\SweetDOM\PatchedNodes;

use s9e\SweetDOM\Attr as ParentClass;
use s9e\SweetDOM\NodeTraits\NodePolyfill;

class Attr extends ParentClass
{
use NodePolyfill;
}
2 changes: 2 additions & 0 deletions src/PatchedNodes/CdataSection.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@

use s9e\SweetDOM\CdataSection as ParentClass;
use s9e\SweetDOM\NodeTraits\ChildNodeWorkarounds;
use s9e\SweetDOM\NodeTraits\NodePolyfill;

class CdataSection extends ParentClass
{
use ChildNodeWorkarounds;
use NodePolyfill;
}
2 changes: 2 additions & 0 deletions src/PatchedNodes/Comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@

use s9e\SweetDOM\Comment as ParentClass;
use s9e\SweetDOM\NodeTraits\ChildNodeWorkarounds;
use s9e\SweetDOM\NodeTraits\NodePolyfill;

class Comment extends ParentClass
{
use ChildNodeWorkarounds;
use NodePolyfill;
}
2 changes: 2 additions & 0 deletions src/PatchedNodes/DocumentFragment.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
namespace s9e\SweetDOM\PatchedNodes;

use s9e\SweetDOM\DocumentFragment as ParentClass;
use s9e\SweetDOM\NodeTraits\NodePolyfill;
use s9e\SweetDOM\NodeTraits\ParentNodePolyfill;
use s9e\SweetDOM\NodeTraits\ParentNodeWorkarounds;

class DocumentFragment extends ParentClass
{
use NodePolyfill;
use ParentNodePolyfill;
use ParentNodeWorkarounds;
}
2 changes: 2 additions & 0 deletions src/PatchedNodes/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@

use s9e\SweetDOM\Element as ParentClass;
use s9e\SweetDOM\NodeTraits\ChildNodeWorkarounds;
use s9e\SweetDOM\NodeTraits\NodePolyfill;
use s9e\SweetDOM\NodeTraits\ParentNodePolyfill;
use s9e\SweetDOM\NodeTraits\ParentNodeWorkarounds;

class Element extends ParentClass
{
use ChildNodeWorkarounds;
use NodePolyfill;
use ParentNodePolyfill;
use ParentNodeWorkarounds;
}
2 changes: 2 additions & 0 deletions src/PatchedNodes/Text.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
namespace s9e\SweetDOM\PatchedNodes;

use s9e\SweetDOM\NodeTraits\ChildNodeWorkarounds;
use s9e\SweetDOM\NodeTraits\NodePolyfill;
use s9e\SweetDOM\Text as ParentClass;

class Text extends ParentClass
{
use ChildNodeWorkarounds;
use NodePolyfill;
}
Loading

0 comments on commit b07ae8f

Please sign in to comment.