From ea794e9cde131e6a8679043d2f1a5983f7828245 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 11 Jul 2023 22:34:39 +0200 Subject: [PATCH] Implement DOMNode::contains() ref: https://dom.spec.whatwg.org/#dom-node-contains --- NEWS | 3 + UPGRADING | 3 + ext/dom/node.c | 36 +++++++++ ext/dom/php_dom.stub.php | 2 + ext/dom/php_dom_arginfo.h | 8 +- ext/dom/tests/DOMNode_contains.phpt | 109 ++++++++++++++++++++++++++++ 6 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 ext/dom/tests/DOMNode_contains.phpt diff --git a/NEWS b/NEWS index 0859ac0f333..41584343fc3 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,9 @@ PHP NEWS . Added zend_call_stack_get implementation for OpenBSD. (David Carlier) . Fixed oss-fuzz #60411 (Fix double-compilation of arrow-functions). (ilutov) +- DOM: + . Added DOMNode::contains() and DOMNameSpaceNode::contains(). (nielsdos) + - Intl: . Fix memory leak in MessageFormatter::format() on failure. (Girgias) diff --git a/UPGRADING b/UPGRADING index e13a467ba49..73c400e047f 100644 --- a/UPGRADING +++ b/UPGRADING @@ -238,6 +238,9 @@ PHP 8.3 UPGRADE NOTES 6. New Functions ======================================== +- DOM: + . Added DOMNode::contains() and DOMNameSpaceNode::contains(). + - JSON: . Added json_validate(), which returns whether the json is valid for the given $depth and $options. diff --git a/ext/dom/node.c b/ext/dom/node.c index 14fa030ccda..c5d81ea74ab 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -1782,4 +1782,40 @@ PHP_METHOD(DOMNode, getLineNo) } /* }}} */ +/* {{{ URL: https://dom.spec.whatwg.org/#dom-node-contains +Since: +*/ +PHP_METHOD(DOMNode, contains) +{ + zval *other, *id; + xmlNodePtr otherp, thisp; + dom_object *unused_intern; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OR_NULL(other) + ZEND_PARSE_PARAMETERS_END(); + + if (other == NULL) { + RETURN_FALSE; + } + + if (UNEXPECTED(!instanceof_function(Z_OBJCE_P(other), dom_node_class_entry) && !instanceof_function(Z_OBJCE_P(other), dom_namespace_node_class_entry))) { + zend_argument_type_error(1, "must be of type DOMNode|DOMNameSpaceNode|null, %s given", zend_zval_value_name(other)); + RETURN_THROWS(); + } + + DOM_GET_OBJ(otherp, other, xmlNodePtr, unused_intern); + DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, unused_intern); + + do { + if (otherp == thisp) { + RETURN_TRUE; + } + otherp = otherp->parent; + } while (otherp); + + RETURN_FALSE; +} +/* }}} */ + #endif diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index 741f7bf1d9e..3c3cf649293 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -389,6 +389,8 @@ class DOMNode /** @return DOMNode|false */ public function replaceChild(DOMNode $node, DOMNode $child) {} + + public function contains(DOMNode|DOMNameSpaceNode|null $other): bool {} } /** @not-serializable */ diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index 1459a9891bb..cf47f1dae4f 100644 --- a/ext/dom/php_dom_arginfo.h +++ b/ext/dom/php_dom_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 00d59fd45c44eb14bbf8f51ee4f61e0464786d69 */ + * Stub hash: fd3acd731f178c2f1034b206b46d53c236823b9f */ 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) @@ -100,6 +100,10 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DOMNode_replaceChild, 0, 0, 2) ZEND_ARG_OBJ_INFO(0, child, DOMNode, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMNode_contains, 0, 1, _IS_BOOL, 0) + ZEND_ARG_OBJ_TYPE_MASK(0, other, DOMNode|DOMNameSpaceNode, MAY_BE_NULL, NULL) +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) @@ -512,6 +516,7 @@ ZEND_METHOD(DOMNode, lookupPrefix); ZEND_METHOD(DOMNode, normalize); ZEND_METHOD(DOMNode, removeChild); ZEND_METHOD(DOMNode, replaceChild); +ZEND_METHOD(DOMNode, contains); ZEND_METHOD(DOMImplementation, getFeature); ZEND_METHOD(DOMImplementation, hasFeature); ZEND_METHOD(DOMImplementation, createDocumentType); @@ -693,6 +698,7 @@ static const zend_function_entry class_DOMNode_methods[] = { ZEND_ME(DOMNode, normalize, arginfo_class_DOMNode_normalize, ZEND_ACC_PUBLIC) ZEND_ME(DOMNode, removeChild, arginfo_class_DOMNode_removeChild, ZEND_ACC_PUBLIC) ZEND_ME(DOMNode, replaceChild, arginfo_class_DOMNode_replaceChild, ZEND_ACC_PUBLIC) + ZEND_ME(DOMNode, contains, arginfo_class_DOMNode_contains, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/dom/tests/DOMNode_contains.phpt b/ext/dom/tests/DOMNode_contains.phpt new file mode 100644 index 00000000000..5c18963804c --- /dev/null +++ b/ext/dom/tests/DOMNode_contains.phpt @@ -0,0 +1,109 @@ +--TEST-- +DOMNode::contains() +--EXTENSIONS-- +dom +--FILE-- +loadXML(<< + + + my title + + +
+

Hello, world!

+

Second paragraph

+

container

+ +
+ + +XML); + +$xpath = new DOMXPath($dom); +$head = $xpath->query("//head")[0]; +$main = $xpath->query("//main")[0]; +$div = $xpath->query("//div")[0]; + +echo "--- False edge cases ---\n"; + +var_dump($dom->documentElement->contains(null)); + +try { + var_dump($dom->contains(new stdClass)); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +echo "--- True cases ---\n"; +var_dump($dom->documentElement->contains($head)); +var_dump($dom->documentElement->contains($main)); +var_dump($dom->contains($dom)); +var_dump($main->contains($main)); +var_dump($div->contains($div)); +var_dump($main->contains($div)); +var_dump($main->contains($main->firstElementChild)); +var_dump($main->contains($div->firstElementChild)); +var_dump($div->contains($div->firstElementChild)); +var_dump($main->contains($main->firstElementChild->firstChild)); +var_dump($dom->contains($dom->doctype)); +var_dump($dom->contains($dom->doctype)); +var_dump($dom->contains($dom->documentElement->getAttributeNode('xmlns:test'))); +var_dump($dom->contains($main->lastChild)); + +echo "--- False cases ---\n"; +var_dump($main->firstElementChild->contains($main)); +var_dump($main->contains($head)); +var_dump($div->contains($main)); +var_dump($main->contains($head->firstElementChild)); +var_dump($div->contains($main->firstElementChild)); +var_dump($div->contains($main->firstElementChild->nextElementSibling)); +var_dump($div->contains($main->lastChild)); + +echo "--- False, create element case ---\n"; + +$newElement = $dom->createElement('x'); +var_dump($dom->documentElement->contains($newElement)); + +echo "--- Removal case ---\n"; + +$main->remove(); +var_dump($main->contains($main)); +var_dump($dom->contains($main)); + +?> +--EXPECT-- +--- False edge cases --- +bool(false) +DOMNode::contains(): Argument #1 ($other) must be of type DOMNode|DOMNameSpaceNode|null, stdClass given +--- True cases --- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +--- False cases --- +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +--- False, create element case --- +bool(false) +--- Removal case --- +bool(true) +bool(false)