1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Add DOMNode::compareDocumentPosition() (#12146)

Reference: https://dom.spec.whatwg.org/#dom-node-comparedocumentposition
This commit is contained in:
Niels Dossche
2023-09-09 01:14:26 +02:00
committed by GitHub
parent 8c2c69494e
commit 880faa39e8
19 changed files with 825 additions and 4 deletions

3
NEWS
View File

@@ -8,4 +8,7 @@ Core:
. Fixed bug GH-12102 (Incorrect compile error when using array access on TMP
value in function call). (ilutov)
DOM:
. Added DOMNode::compareDocumentPosition(). (nielsdos)
<<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>

View File

@@ -19,10 +19,25 @@ PHP 8.4 UPGRADE NOTES
1. Backward Incompatible Changes
========================================
- DOM:
. New methods and constants were added to some DOM classes. If you inherit
from these and you happen to have a method or property with the same name,
you might encounter errors if the declaration is incompatible.
Consult sections 2. New Features and 6. New Functions for a list of
newly implemented methods and constants.
========================================
2. New Features
========================================
- DOM:
. Added constant DOMNode::DOCUMENT_POSITION_DISCONNECTED.
. Added constant DOMNode::DOCUMENT_POSITION_PRECEDING.
. Added constant DOMNode::DOCUMENT_POSITION_FOLLOWING.
. Added constant DOMNode::DOCUMENT_POSITION_CONTAINS.
. Added constant DOMNode::DOCUMENT_POSITION_CONTAINED_BY.
. Added constant DOMNode::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC.
========================================
3. Changes in SAPI modules
========================================
@@ -39,6 +54,9 @@ PHP 8.4 UPGRADE NOTES
6. New Functions
========================================
- DOM:
. Added DOMNode::compareDocumentPosition().
========================================
7. New Classes and Interfaces
========================================

View File

@@ -1518,6 +1518,15 @@ static bool php_dom_node_is_equal_node(const xmlNode *this, const xmlNode *other
PHP_DOM_DEFINE_LIST_EQUALITY_HELPER(xmlNode)
PHP_DOM_DEFINE_LIST_EQUALITY_HELPER(xmlNs)
static bool php_dom_is_equal_attr(const xmlAttr *this_attr, const xmlAttr *other_attr)
{
ZEND_ASSERT(this_attr != NULL);
ZEND_ASSERT(other_attr != NULL);
return xmlStrEqual(this_attr->name, other_attr->name)
&& php_dom_node_is_ns_uri_equal((const xmlNode *) this_attr, (const xmlNode *) other_attr)
&& php_dom_node_is_content_equal((const xmlNode *) this_attr, (const xmlNode *) other_attr);
}
static bool php_dom_node_is_equal_node(const xmlNode *this, const xmlNode *other)
{
ZEND_ASSERT(this != NULL);
@@ -1552,9 +1561,7 @@ static bool php_dom_node_is_equal_node(const xmlNode *this, const xmlNode *other
} else if (this->type == XML_ATTRIBUTE_NODE) {
const xmlAttr *this_attr = (const xmlAttr *) this;
const xmlAttr *other_attr = (const xmlAttr *) other;
return xmlStrEqual(this_attr->name, other_attr->name)
&& php_dom_node_is_ns_uri_equal(this, other)
&& php_dom_node_is_content_equal(this, other);
return php_dom_is_equal_attr(this_attr, other_attr);
} else if (this->type == XML_ENTITY_REF_NODE) {
return xmlStrEqual(this->name, other->name);
} else if (this->type == XML_ENTITY_DECL || this->type == XML_NOTATION_NODE || this->type == XML_ENTITY_NODE) {
@@ -2030,4 +2037,170 @@ PHP_METHOD(DOMNode, getRootNode)
}
/* }}} */
/* {{{ URL: https://dom.spec.whatwg.org/#dom-node-comparedocumentposition (last check date 2023-07-24)
Since:
*/
#define DOCUMENT_POSITION_DISCONNECTED 0x01
#define DOCUMENT_POSITION_PRECEDING 0x02
#define DOCUMENT_POSITION_FOLLOWING 0x04
#define DOCUMENT_POSITION_CONTAINS 0x08
#define DOCUMENT_POSITION_CONTAINED_BY 0x10
#define DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC 0x20
PHP_METHOD(DOMNode, compareDocumentPosition)
{
zval *id, *node_zval;
xmlNodePtr other, this;
dom_object *this_intern, *other_intern;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &node_zval, dom_node_class_entry) == FAILURE) {
RETURN_THROWS();
}
DOM_GET_THIS_OBJ(this, id, xmlNodePtr, this_intern);
DOM_GET_OBJ(other, node_zval, xmlNodePtr, other_intern);
/* Step 1 */
if (this == other) {
RETURN_LONG(0);
}
/* Step 2 */
xmlNodePtr node1 = other;
xmlNodePtr node2 = this;
/* Step 3 */
xmlNodePtr attr1 = NULL;
xmlNodePtr attr2 = NULL;
/* Step 4 */
if (node1->type == XML_ATTRIBUTE_NODE) {
attr1 = node1;
node1 = attr1->parent;
}
/* Step 5 */
if (node2->type == XML_ATTRIBUTE_NODE) {
/* 5.1 */
attr2 = node2;
node2 = attr2->parent;
/* 5.2 */
if (attr1 != NULL && node1 != NULL && node2 == node1) {
for (const xmlAttr *attr = node2->properties; attr != NULL; attr = attr->next) {
if (php_dom_is_equal_attr(attr, (const xmlAttr *) attr1)) {
RETURN_LONG(DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | DOCUMENT_POSITION_PRECEDING);
} else if (php_dom_is_equal_attr(attr, (const xmlAttr *) attr2)) {
RETURN_LONG(DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | DOCUMENT_POSITION_FOLLOWING);
}
}
}
}
/* Step 6 */
/* We first check the first condition,
* and as we need the root later anyway we'll cache the root and perform the root check after this if. */
if (node1 == NULL || node2 == NULL) {
goto disconnected;
}
bool node2_is_ancestor_of_node1 = false;
size_t node1_depth = 0;
xmlNodePtr node1_root = node1;
while (node1_root->parent) {
node1_root = node1_root->parent;
if (node1_root == node2) {
node2_is_ancestor_of_node1 = true;
}
node1_depth++;
}
bool node1_is_ancestor_of_node2 = false;
size_t node2_depth = 0;
xmlNodePtr node2_root = node2;
while (node2_root->parent) {
node2_root = node2_root->parent;
if (node2_root == node1) {
node1_is_ancestor_of_node2 = true;
}
node2_depth++;
}
/* Second condition from step 6 */
if (node1_root != node2_root) {
goto disconnected;
}
/* Step 7 */
if ((node1_is_ancestor_of_node2 && attr1 == NULL) || (node1 == node2 && attr2 != NULL)) {
RETURN_LONG(DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING);
}
/* Step 8 */
if ((node2_is_ancestor_of_node1 && attr2 == NULL) || (node1 == node2 && attr1 != NULL)) {
RETURN_LONG(DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING);
}
/* Special case: comparing children and attributes.
* They belong to a different tree and are therefore hard to compare, but spec demands attributes to precede children
* according to the pre-order depth-first search ordering.
* Because their tree is different, the node parents only meet at the common element instead of earlier.
* Therefore, it seems that one is the ancestor of the other. */
if (node1_is_ancestor_of_node2) {
ZEND_ASSERT(attr1 != NULL); /* Would've been handled in step 7 otherwise */
RETURN_LONG(DOCUMENT_POSITION_PRECEDING);
} else if (node2_is_ancestor_of_node1) {
ZEND_ASSERT(attr2 != NULL); /* Would've been handled in step 8 otherwise */
RETURN_LONG(DOCUMENT_POSITION_FOLLOWING);
}
/* Step 9 */
/* We'll use the following strategy (which was already prepared during step 6) to implement this efficiently:
* 1. Move nodes upwards such that they are at the same depth.
* 2. Then we move both nodes upwards simultaneously until their parents are equal.
* 3. If we then move node1 to the next entry repeatedly and we encounter node2,
* then we know node1 precedes node2. Otherwise, node2 must precede node1. */
/* 1. */
if (node1_depth > node2_depth) {
do {
node1 = node1->parent;
node1_depth--;
} while (node1_depth > node2_depth);
} else if (node2_depth > node1_depth) {
do {
node2 = node2->parent;
node2_depth--;
} while (node2_depth > node1_depth);
}
/* 2. */
while (node1->parent != node2->parent) {
node1 = node1->parent;
node2 = node2->parent;
}
/* 3. */
ZEND_ASSERT(node1 != node2);
ZEND_ASSERT(node1 != NULL);
ZEND_ASSERT(node2 != NULL);
do {
node1 = node1->next;
if (node1 == node2) {
RETURN_LONG(DOCUMENT_POSITION_PRECEDING);
}
} while (node1 != NULL);
/* Step 10 */
RETURN_LONG(DOCUMENT_POSITION_FOLLOWING);
disconnected:;
zend_long ordering;
if (UNEXPECTED(node1 == node2)) {
/* Degenerate case, they're both NULL, but the ordering must be consistent... */
ZEND_ASSERT(node1 == NULL);
ordering = other_intern < this_intern ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING;
} else {
ordering = node1 < node2 ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING;
}
RETURN_LONG(DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | ordering);
}
/* }}} */
#endif

View File

@@ -297,6 +297,13 @@ interface DOMChildNode
/** @not-serializable */
class DOMNode
{
public const int DOCUMENT_POSITION_DISCONNECTED = 0x01;
public const int DOCUMENT_POSITION_PRECEDING = 0x02;
public const int DOCUMENT_POSITION_FOLLOWING = 0x04;
public const int DOCUMENT_POSITION_CONTAINS = 0x08;
public const int DOCUMENT_POSITION_CONTAINED_BY = 0x10;
public const int DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20;
/** @readonly */
public string $nodeName;
@@ -404,6 +411,8 @@ class DOMNode
public function contains(DOMNode|DOMNameSpaceNode|null $other): bool {}
public function getRootNode(?array $options = null): DOMNode {}
public function compareDocumentPosition(DOMNode $other): int {}
}
/** @not-serializable */

View File

@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: ebe9bcbd185e1973b5447beb306bd9d93051f415 */
* Stub hash: 4705229124ee243538e712f4af1d94ddc4c3be0b */
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0)
ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0)
@@ -114,6 +114,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_DOMNode_getRootNode, 0, 0,
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMNode_compareDocumentPosition, 0, 1, IS_LONG, 0)
ZEND_ARG_OBJ_INFO(0, other, DOMNode, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_DOMImplementation_getFeature, 0, 2, IS_NEVER, 0)
ZEND_ARG_TYPE_INFO(0, feature, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, version, IS_STRING, 0)
@@ -553,6 +557,7 @@ ZEND_METHOD(DOMNode, removeChild);
ZEND_METHOD(DOMNode, replaceChild);
ZEND_METHOD(DOMNode, contains);
ZEND_METHOD(DOMNode, getRootNode);
ZEND_METHOD(DOMNode, compareDocumentPosition);
ZEND_METHOD(DOMImplementation, getFeature);
ZEND_METHOD(DOMImplementation, hasFeature);
ZEND_METHOD(DOMImplementation, createDocumentType);
@@ -745,6 +750,7 @@ static const zend_function_entry class_DOMNode_methods[] = {
ZEND_ME(DOMNode, replaceChild, arginfo_class_DOMNode_replaceChild, ZEND_ACC_PUBLIC)
ZEND_ME(DOMNode, contains, arginfo_class_DOMNode_contains, ZEND_ACC_PUBLIC)
ZEND_ME(DOMNode, getRootNode, arginfo_class_DOMNode_getRootNode, ZEND_ACC_PUBLIC)
ZEND_ME(DOMNode, compareDocumentPosition, arginfo_class_DOMNode_compareDocumentPosition, ZEND_ACC_PUBLIC)
ZEND_FE_END
};
@@ -1098,6 +1104,42 @@ static zend_class_entry *register_class_DOMNode(void)
class_entry = zend_register_internal_class_ex(&ce, NULL);
class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
zval const_DOCUMENT_POSITION_DISCONNECTED_value;
ZVAL_LONG(&const_DOCUMENT_POSITION_DISCONNECTED_value, 0x1);
zend_string *const_DOCUMENT_POSITION_DISCONNECTED_name = zend_string_init_interned("DOCUMENT_POSITION_DISCONNECTED", sizeof("DOCUMENT_POSITION_DISCONNECTED") - 1, 1);
zend_declare_typed_class_constant(class_entry, const_DOCUMENT_POSITION_DISCONNECTED_name, &const_DOCUMENT_POSITION_DISCONNECTED_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(const_DOCUMENT_POSITION_DISCONNECTED_name);
zval const_DOCUMENT_POSITION_PRECEDING_value;
ZVAL_LONG(&const_DOCUMENT_POSITION_PRECEDING_value, 0x2);
zend_string *const_DOCUMENT_POSITION_PRECEDING_name = zend_string_init_interned("DOCUMENT_POSITION_PRECEDING", sizeof("DOCUMENT_POSITION_PRECEDING") - 1, 1);
zend_declare_typed_class_constant(class_entry, const_DOCUMENT_POSITION_PRECEDING_name, &const_DOCUMENT_POSITION_PRECEDING_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(const_DOCUMENT_POSITION_PRECEDING_name);
zval const_DOCUMENT_POSITION_FOLLOWING_value;
ZVAL_LONG(&const_DOCUMENT_POSITION_FOLLOWING_value, 0x4);
zend_string *const_DOCUMENT_POSITION_FOLLOWING_name = zend_string_init_interned("DOCUMENT_POSITION_FOLLOWING", sizeof("DOCUMENT_POSITION_FOLLOWING") - 1, 1);
zend_declare_typed_class_constant(class_entry, const_DOCUMENT_POSITION_FOLLOWING_name, &const_DOCUMENT_POSITION_FOLLOWING_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(const_DOCUMENT_POSITION_FOLLOWING_name);
zval const_DOCUMENT_POSITION_CONTAINS_value;
ZVAL_LONG(&const_DOCUMENT_POSITION_CONTAINS_value, 0x8);
zend_string *const_DOCUMENT_POSITION_CONTAINS_name = zend_string_init_interned("DOCUMENT_POSITION_CONTAINS", sizeof("DOCUMENT_POSITION_CONTAINS") - 1, 1);
zend_declare_typed_class_constant(class_entry, const_DOCUMENT_POSITION_CONTAINS_name, &const_DOCUMENT_POSITION_CONTAINS_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(const_DOCUMENT_POSITION_CONTAINS_name);
zval const_DOCUMENT_POSITION_CONTAINED_BY_value;
ZVAL_LONG(&const_DOCUMENT_POSITION_CONTAINED_BY_value, 0x10);
zend_string *const_DOCUMENT_POSITION_CONTAINED_BY_name = zend_string_init_interned("DOCUMENT_POSITION_CONTAINED_BY", sizeof("DOCUMENT_POSITION_CONTAINED_BY") - 1, 1);
zend_declare_typed_class_constant(class_entry, const_DOCUMENT_POSITION_CONTAINED_BY_name, &const_DOCUMENT_POSITION_CONTAINED_BY_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(const_DOCUMENT_POSITION_CONTAINED_BY_name);
zval const_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC_value;
ZVAL_LONG(&const_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC_value, 0x20);
zend_string *const_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC_name = zend_string_init_interned("DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC", sizeof("DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC") - 1, 1);
zend_declare_typed_class_constant(class_entry, const_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC_name, &const_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(const_DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC_name);
zval property_nodeName_default_value;
ZVAL_UNDEF(&property_nodeName_default_value);
zend_string *property_nodeName_name = zend_string_init("nodeName", sizeof("nodeName") - 1, 1);

View File

@@ -0,0 +1,53 @@
--TEST--
compareDocumentPosition: attribute vs child order
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$dom->loadXML(<<<XML
<?xml version="1.0"?>
<container>
<before/>
<outer align="center">
<p>foo</p>
<div>
<div>
<p>bar</p>
</div>
</div>
</outer>
</container>
XML);
$before = $dom->documentElement->firstElementChild;
$outer = $before->nextElementSibling;
$foo = $outer->firstElementChild;
$bar = $foo->nextElementSibling->firstElementChild->firstElementChild;
// See note about attributes vs children positions: attributes precede children
echo "--- outer attribute vs before ---\n";
var_dump($outer->attributes[0]->compareDocumentPosition($before) === DOMNode::DOCUMENT_POSITION_PRECEDING);
var_dump($before->compareDocumentPosition($outer->attributes[0]) === DOMNode::DOCUMENT_POSITION_FOLLOWING);
echo "--- outer attribute vs foo ---\n";
var_dump($outer->attributes[0]->compareDocumentPosition($foo) === DOMNode::DOCUMENT_POSITION_FOLLOWING);
var_dump($foo->compareDocumentPosition($outer->attributes[0]) === DOMNode::DOCUMENT_POSITION_PRECEDING);
echo "--- outer attribute vs bar ---\n";
var_dump($outer->attributes[0]->compareDocumentPosition($bar) === DOMNode::DOCUMENT_POSITION_FOLLOWING);
var_dump($bar->compareDocumentPosition($outer->attributes[0]) === DOMNode::DOCUMENT_POSITION_PRECEDING);
?>
--EXPECT--
--- outer attribute vs before ---
bool(true)
bool(true)
--- outer attribute vs foo ---
bool(true)
bool(true)
--- outer attribute vs bar ---
bool(true)
bool(true)

View File

@@ -0,0 +1,54 @@
--TEST--
compareDocumentPosition: attribute order for different element
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$dom->loadXML(<<<XML
<?xml version="1.0"?>
<outer>
<inner a="b" c="d" e="f" />
<inner a="b" x="y" />
</outer>
XML);
$attrs1 = $dom->documentElement->firstElementChild->attributes;
$attrs2 = $dom->documentElement->firstElementChild->nextElementSibling->attributes;
foreach ($attrs1 as $attr1) {
foreach ($attrs2 as $attr2) {
echo "--- Compare 1->2 {$attr1->name} and {$attr2->name} ---\n";
var_dump($attr1->compareDocumentPosition($attr2) === DOMNode::DOCUMENT_POSITION_FOLLOWING);
echo "--- Compare 2->1 {$attr1->name} and {$attr2->name} ---\n";
var_dump($attr2->compareDocumentPosition($attr1) === DOMNode::DOCUMENT_POSITION_PRECEDING);
}
}
?>
--EXPECT--
--- Compare 1->2 a and a ---
bool(true)
--- Compare 2->1 a and a ---
bool(true)
--- Compare 1->2 a and x ---
bool(true)
--- Compare 2->1 a and x ---
bool(true)
--- Compare 1->2 c and a ---
bool(true)
--- Compare 2->1 c and a ---
bool(true)
--- Compare 1->2 c and x ---
bool(true)
--- Compare 2->1 c and x ---
bool(true)
--- Compare 1->2 e and a ---
bool(true)
--- Compare 2->1 e and a ---
bool(true)
--- Compare 1->2 e and x ---
bool(true)
--- Compare 2->1 e and x ---
bool(true)

View File

@@ -0,0 +1,40 @@
--TEST--
compareDocumentPosition: attribute order for same element
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$dom->loadXML(<<<XML
<?xml version="1.0"?>
<container a="b" c="d" e="f" />
XML);
$attrs = $dom->documentElement->attributes;
for ($i = 0; $i <= 2; $i++) {
for ($j = $i + 1; $j <= $i + 2; $j++) {
echo "--- Compare $i and ", ($j % 3), " ---\n";
if ($i < ($j % 3)) {
$expected = DOMNode::DOCUMENT_POSITION_FOLLOWING | DOMNode::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
} else {
$expected = DOMNode::DOCUMENT_POSITION_PRECEDING | DOMNode::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
}
var_dump($attrs[$i]->compareDocumentPosition($attrs[$j % 3]) === $expected);
}
}
?>
--EXPECT--
--- Compare 0 and 1 ---
bool(true)
--- Compare 0 and 2 ---
bool(true)
--- Compare 1 and 2 ---
bool(true)
--- Compare 1 and 0 ---
bool(true)
--- Compare 2 and 0 ---
bool(true)
--- Compare 2 and 1 ---
bool(true)

View File

@@ -0,0 +1,23 @@
--TEST--
compareDocumentPosition: contains attribute as a direct descendent
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$dom->loadXML(<<<XML
<?xml version="1.0"?>
<container align="center"/>
XML);
$container = $dom->documentElement;
$attribute = $container->attributes[0];
var_dump($container->compareDocumentPosition($attribute) === (DOMNode::DOCUMENT_POSITION_FOLLOWING | DOMNode::DOCUMENT_POSITION_CONTAINED_BY));
var_dump($attribute->compareDocumentPosition($container) === (DOMNode::DOCUMENT_POSITION_PRECEDING | DOMNode::DOCUMENT_POSITION_CONTAINS));
?>
--EXPECT--
bool(true)
bool(true)

View File

@@ -0,0 +1,22 @@
--TEST--
compareDocumentPosition: contains attribute for a freestanding element
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$foo = $dom->createElement("foo");
$foo->setAttribute("test", "value");
$attribute = $foo->attributes[0];
echo $dom->saveXML($foo), "\n";
var_dump($foo->compareDocumentPosition($attribute) === (DOMNode::DOCUMENT_POSITION_FOLLOWING | DOMNode::DOCUMENT_POSITION_CONTAINED_BY));
var_dump($attribute->compareDocumentPosition($foo) === (DOMNode::DOCUMENT_POSITION_PRECEDING | DOMNode::DOCUMENT_POSITION_CONTAINS));
?>
--EXPECT--
<foo test="value"/>
bool(true)
bool(true)

View File

@@ -0,0 +1,28 @@
--TEST--
compareDocumentPosition: contains attribute as a descendent in a longer path
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$dom->loadXML(<<<XML
<?xml version="1.0"?>
<container>
<div>
<p align="center"/>
</div>
</container>
XML);
$container = $dom->documentElement;
$p = $container->firstElementChild->firstElementChild;
$attribute = $p->attributes[0];
var_dump($container->compareDocumentPosition($attribute) === (DOMNode::DOCUMENT_POSITION_FOLLOWING | DOMNode::DOCUMENT_POSITION_CONTAINED_BY));
var_dump($attribute->compareDocumentPosition($container) === (DOMNode::DOCUMENT_POSITION_PRECEDING | DOMNode::DOCUMENT_POSITION_CONTAINS));
?>
--EXPECT--
bool(true)
bool(true)

View File

@@ -0,0 +1,25 @@
--TEST--
compareDocumentPosition: contains nodes as a direct descendent
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$dom->loadXML(<<<XML
<?xml version="1.0"?>
<container>
<div/>
</container>
XML);
$container = $dom->documentElement;
$div = $container->firstChild;
var_dump($container->compareDocumentPosition($div) === (DOMNode::DOCUMENT_POSITION_FOLLOWING | DOMNode::DOCUMENT_POSITION_CONTAINED_BY));
var_dump($div->compareDocumentPosition($container) === (DOMNode::DOCUMENT_POSITION_PRECEDING | DOMNode::DOCUMENT_POSITION_CONTAINS));
?>
--EXPECT--
bool(true)
bool(true)

View File

@@ -0,0 +1,44 @@
--TEST--
compareDocumentPosition: contains nodes as a descendent in a longer path
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$dom->loadXML(<<<XML
<?xml version="1.0"?>
<container>
<a>
<b/>
<c>
<d/>
</c>
</a>
</container>
XML);
$xpath = new DOMXPath($dom);
$container = $xpath->query("//container")->item(0);
foreach (["a", "b", "c", "d"] as $tag) {
echo "--- Test $tag ---\n";
$element = $xpath->query("//$tag")->item(0);
var_dump($container->compareDocumentPosition($element) === (DOMNode::DOCUMENT_POSITION_FOLLOWING | DOMNode::DOCUMENT_POSITION_CONTAINED_BY));
var_dump($element->compareDocumentPosition($container) === (DOMNode::DOCUMENT_POSITION_PRECEDING | DOMNode::DOCUMENT_POSITION_CONTAINS));
}
?>
--EXPECT--
--- Test a ---
bool(true)
bool(true)
--- Test b ---
bool(true)
bool(true)
--- Test c ---
bool(true)
bool(true)
--- Test d ---
bool(true)
bool(true)

View File

@@ -0,0 +1,68 @@
--TEST--
compareDocumentPosition: disconnected
--EXTENSIONS--
dom
--FILE--
<?php
function check($element1, $element2) {
echo "Disconnect and implementation flag (1->2): ";
var_dump(($element1->compareDocumentPosition($element2) & (DOMNode::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | DOMNode::DOCUMENT_POSITION_DISCONNECTED)) === (DOMNode::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | DOMNode::DOCUMENT_POSITION_DISCONNECTED));
echo "Disconnect and implementation flag (2->1): ";
var_dump(($element2->compareDocumentPosition($element1) & (DOMNode::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | DOMNode::DOCUMENT_POSITION_DISCONNECTED)) === (DOMNode::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | DOMNode::DOCUMENT_POSITION_DISCONNECTED));
// Must be the opposite result
echo "Opposite result: ";
if ($element1->compareDocumentPosition($element2) & DOMNode::DOCUMENT_POSITION_FOLLOWING) {
var_dump(($element2->compareDocumentPosition($element1) & DOMNode::DOCUMENT_POSITION_PRECEDING) === DOMNode::DOCUMENT_POSITION_PRECEDING);
} else {
var_dump(($element2->compareDocumentPosition($element1) & DOMNode::DOCUMENT_POSITION_FOLLOWING) === DOMNode::DOCUMENT_POSITION_FOLLOWING);
}
}
$dom1 = new DOMDocument();
$dom1->loadXML('<?xml version="1.0"?><container/>');
$dom2 = new DOMDocument();
$dom2->loadXML('<?xml version="1.0"?><container/>');
echo "--- Two documents ---\n";
check($dom1, $dom2);
echo "--- Two document roots ---\n";
check($dom1->documentElement, $dom2->documentElement);
echo "--- Fragment ---\n";
$fragment = $dom1->createDocumentFragment();
$foo = $fragment->appendChild(new DOMText("foo"));
check($foo, $dom1);
echo "--- Unattached element ---\n";
check(new DOMElement("foo"), new DOMElement("bar"));
echo "--- Unattached attribute ---\n";
check(new DOMAttr("foo"), new DOMAttr("bar"));
echo "--- Unattached attribute & element ---\n";
check(new DOMAttr("foo"), new DOMElement("bar"));
?>
--EXPECT--
--- Two documents ---
Disconnect and implementation flag (1->2): bool(true)
Disconnect and implementation flag (2->1): bool(true)
Opposite result: bool(true)
--- Two document roots ---
Disconnect and implementation flag (1->2): bool(true)
Disconnect and implementation flag (2->1): bool(true)
Opposite result: bool(true)
--- Fragment ---
Disconnect and implementation flag (1->2): bool(true)
Disconnect and implementation flag (2->1): bool(true)
Opposite result: bool(true)
--- Unattached element ---
Disconnect and implementation flag (1->2): bool(true)
Disconnect and implementation flag (2->1): bool(true)
Opposite result: bool(true)
--- Unattached attribute ---
Disconnect and implementation flag (1->2): bool(true)
Disconnect and implementation flag (2->1): bool(true)
Opposite result: bool(true)
--- Unattached attribute & element ---
Disconnect and implementation flag (1->2): bool(true)
Disconnect and implementation flag (2->1): bool(true)
Opposite result: bool(true)

View File

@@ -0,0 +1,50 @@
--TEST--
compareDocumentPosition: element order at a different tree depth
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$dom->loadXML(<<<XML
<?xml version="1.0"?>
<container>
<div>
<p>foo</p>
</div>
<div>
<div>
<p>bar</p>
</div>
</div>
</container>
XML);
$xpath = new DOMXPath($dom);
$query = $xpath->query("//p");
$foo = $query->item(0);
$bar = $query->item(1);
for ($i = 0; $i < 2; $i++) {
echo "--- Check on depth $i ---\n";
var_dump($foo->compareDocumentPosition($bar) === DOMNode::DOCUMENT_POSITION_FOLLOWING);
var_dump($bar->compareDocumentPosition($foo) === DOMNode::DOCUMENT_POSITION_PRECEDING);
$foo = $foo->parentElement;
$bar = $bar->parentElement;
}
echo "--- One contains the other at depth 2 ---\n";
var_dump(boolval($foo->compareDocumentPosition($bar) & DOMNode::DOCUMENT_POSITION_CONTAINED_BY));
var_dump(boolval($bar->compareDocumentPosition($foo) & DOMNode::DOCUMENT_POSITION_CONTAINS));
?>
--EXPECT--
--- Check on depth 0 ---
bool(true)
bool(true)
--- Check on depth 1 ---
bool(true)
bool(true)
--- One contains the other at depth 2 ---
bool(true)
bool(true)

View File

@@ -0,0 +1,18 @@
--TEST--
compareDocumentPosition: direct root children
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$foo = $dom->appendChild($dom->createElement("foo"));
$bar = $dom->appendChild($dom->createElement("bar"));
var_dump($foo->compareDocumentPosition($bar) === DOMNode::DOCUMENT_POSITION_FOLLOWING);
var_dump($bar->compareDocumentPosition($foo) === DOMNode::DOCUMENT_POSITION_PRECEDING);
?>
--EXPECT--
bool(true)
bool(true)

View File

@@ -0,0 +1,71 @@
--TEST--
compareDocumentPosition: element order at the same tree depth
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$dom->loadXML(<<<XML
<?xml version="1.0"?>
<container>
<div>
<div>
<strong/>
<p>foo</p>
</div>
</div>
<div>
<div>
<p>bar</p>
<strong/>
</div>
</div>
</container>
XML);
$xpath = new DOMXPath($dom);
$query = $xpath->query("//p");
$foo = $query->item(0);
$bar = $query->item(1);
echo "--- strong & foo ---\n";
var_dump($foo->previousElementSibling->compareDocumentPosition($foo) === DOMNode::DOCUMENT_POSITION_FOLLOWING);
var_dump($foo->compareDocumentPosition($foo->previousElementSibling) === DOMNode::DOCUMENT_POSITION_PRECEDING);
echo "--- strong & bar ---\n";
var_dump($bar->nextElementSibling->compareDocumentPosition($bar) === DOMNode::DOCUMENT_POSITION_PRECEDING);
var_dump($bar->compareDocumentPosition($bar->nextElementSibling) === DOMNode::DOCUMENT_POSITION_FOLLOWING);
echo "--- strong & strong ---\n";
var_dump($foo->previousElementSibling->compareDocumentPosition($bar->nextElementSibling) === DOMNode::DOCUMENT_POSITION_FOLLOWING);
var_dump($bar->nextElementSibling->compareDocumentPosition($foo->previousElementSibling) === DOMNode::DOCUMENT_POSITION_PRECEDING);
for ($i = 0; $i < 3; $i++) {
echo "--- Check on depth $i ---\n";
var_dump($foo->compareDocumentPosition($bar) === DOMNode::DOCUMENT_POSITION_FOLLOWING);
var_dump($bar->compareDocumentPosition($foo) === DOMNode::DOCUMENT_POSITION_PRECEDING);
$foo = $foo->parentElement;
$bar = $bar->parentElement;
}
?>
--EXPECT--
--- strong & foo ---
bool(true)
bool(true)
--- strong & bar ---
bool(true)
bool(true)
--- strong & strong ---
bool(true)
bool(true)
--- Check on depth 0 ---
bool(true)
bool(true)
--- Check on depth 1 ---
bool(true)
bool(true)
--- Check on depth 2 ---
bool(true)
bool(true)

View File

@@ -0,0 +1,51 @@
--TEST--
compareDocumentPosition: entity ordering
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$dom->loadXML(<<<XML
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY e1 "e1">
<!ENTITY e2 "e2">
]>
<container>
<child>&e1;</child>
</container>
XML, LIBXML_NOENT);
$entities = iterator_to_array($dom->doctype->entities);
usort($entities, fn ($a, $b) => strcmp($a->nodeName, $b->nodeName));
echo "--- Compare entities ---\n";
var_dump($entities[0]->compareDocumentPosition($entities[1]) === DOMNode::DOCUMENT_POSITION_FOLLOWING);
var_dump($entities[1]->compareDocumentPosition($entities[0]) === DOMNode::DOCUMENT_POSITION_PRECEDING);
$xpath = new DOMXPath($dom);
$child = $xpath->query('//child')->item(0);
echo "--- Compare entities against first child ---\n";
var_dump($entities[0]->compareDocumentPosition($child->firstChild) === DOMNode::DOCUMENT_POSITION_FOLLOWING);
var_dump($entities[1]->compareDocumentPosition($child->firstChild) === DOMNode::DOCUMENT_POSITION_FOLLOWING);
echo "--- Compare first child against entities ---\n";
var_dump($child->firstChild->compareDocumentPosition($entities[0]) === DOMNode::DOCUMENT_POSITION_PRECEDING);
var_dump($child->firstChild->compareDocumentPosition($entities[1]) === DOMNode::DOCUMENT_POSITION_PRECEDING);
?>
--EXPECT--
--- Compare entities ---
bool(true)
bool(true)
--- Compare entities against first child ---
bool(true)
bool(true)
--- Compare first child against entities ---
bool(true)
bool(true)

View File

@@ -0,0 +1,29 @@
--TEST--
compareDocumentPosition: equal nodes
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$dom->loadXML(<<<XML
<?xml version="1.0"?>
<container>
<p>foo</p>
<p>foo</p>
</container>
XML);
$xpath = new DOMXPath($dom);
$nodes = $xpath->query('//p');
var_dump($nodes->item(0)->compareDocumentPosition($nodes->item(0)) === 0);
var_dump($nodes->item(1)->compareDocumentPosition($nodes->item(1)) === 0);
var_dump($nodes->item(0)->compareDocumentPosition($nodes->item(1)) === 0);
var_dump($nodes->item(1)->compareDocumentPosition($nodes->item(0)) === 0);
?>
--EXPECT--
bool(true)
bool(true)
bool(false)
bool(false)