Implement DOMNode::isConnected and DOMNameSpaceNode::isConnected

ref: https://dom.spec.whatwg.org/#dom-node-isconnected

Closes GH-11677.
This commit is contained in:
Niels Dossche 2023-07-11 18:19:12 +02:00
parent 9c5cf6594d
commit d38cc9b9b6
17 changed files with 144 additions and 23 deletions

1
NEWS
View File

@ -25,6 +25,7 @@ PHP NEWS
. Added DOMNode::getRootNode(). (nielsdos)
. Added DOMElement::className and DOMElement::id. (nielsdos)
. Added DOMParentNode::replaceChildren(). (nielsdos)
. Added DOMNode::isConnected and DOMNameSpaceNode::isConnected. (nielsdos)
- FPM:
. Added warning to log when fpm socket was not registered on the expected

View File

@ -268,6 +268,7 @@ PHP 8.3 UPGRADE NOTES
This is not binary-safe at the moment because of underlying limitations of
libxml2.
. Added DOMParentNode::replaceChildren().
. Added DOMNode::isConnected and DOMNameSpaceNode::isConnected.
- JSON:
. Added json_validate(), which returns whether the json is valid for

View File

@ -994,19 +994,6 @@ PHP_METHOD(DOMDocument, getElementsByTagNameNS)
}
/* }}} end dom_document_get_elements_by_tag_name_ns */
static bool php_dom_is_node_attached(const xmlNode *node)
{
ZEND_ASSERT(node != NULL);
node = node->parent;
while (node != NULL) {
if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) {
return true;
}
node = node->parent;
}
return false;
}
/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-getElBId
Since: DOM Level 2
*/
@ -1035,7 +1022,7 @@ PHP_METHOD(DOMDocument, getElementById)
* ingrained in the library, and uses the cache for various purposes, it seems like a bad
* idea and lost cause to fight it. Instead, we'll simply walk the tree upwards to check
* if the node is attached to the document. */
if (attrp && attrp->parent && php_dom_is_node_attached(attrp->parent)) {
if (attrp && attrp->parent && php_dom_is_node_connected(attrp->parent)) {
DOM_RET_OBJ((xmlNodePtr) attrp->parent, &ret, intern);
} else {
RETVAL_NULL();

View File

@ -107,6 +107,7 @@ int dom_node_next_sibling_read(dom_object *obj, zval *retval);
int dom_node_previous_element_sibling_read(dom_object *obj, zval *retval);
int dom_node_next_element_sibling_read(dom_object *obj, zval *retval);
int dom_node_attributes_read(dom_object *obj, zval *retval);
zend_result dom_node_is_connected_read(dom_object *obj, zval *retval);
int dom_node_owner_document_read(dom_object *obj, zval *retval);
int dom_node_namespace_uri_read(dom_object *obj, zval *retval);
int dom_node_prefix_read(dom_object *obj, zval *retval);

View File

@ -52,6 +52,18 @@ zend_string *dom_node_get_node_name_attribute_or_element(const xmlNode *nodep)
}
}
bool php_dom_is_node_connected(const xmlNode *node)
{
ZEND_ASSERT(node != NULL);
do {
if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) {
return true;
}
node = node->parent;
} while (node != NULL);
return false;
}
/* {{{ nodeName string
readonly=yes
URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-F68D095
@ -488,6 +500,25 @@ int dom_node_attributes_read(dom_object *obj, zval *retval)
/* }}} */
/* {{{ isConnected boolean
readonly=yes
URL: https://dom.spec.whatwg.org/#dom-node-isconnected
Since:
*/
zend_result dom_node_is_connected_read(dom_object *obj, zval *retval)
{
xmlNode *nodep = dom_object_get_node(obj);
if (nodep == NULL) {
php_dom_throw_error(INVALID_STATE_ERR, 1);
return FAILURE;
}
ZVAL_BOOL(retval, php_dom_is_node_connected(nodep));
return SUCCESS;
}
/* }}} */
/* {{{ ownerDocument DomDocument
readonly=yes
URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-node-ownerDoc

View File

@ -642,6 +642,7 @@ PHP_MINIT_FUNCTION(dom)
dom_register_prop_handler(&dom_node_prop_handlers, "previousSibling", sizeof("previousSibling")-1, dom_node_previous_sibling_read, NULL);
dom_register_prop_handler(&dom_node_prop_handlers, "nextSibling", sizeof("nextSibling")-1, dom_node_next_sibling_read, NULL);
dom_register_prop_handler(&dom_node_prop_handlers, "attributes", sizeof("attributes")-1, dom_node_attributes_read, NULL);
dom_register_prop_handler(&dom_node_prop_handlers, "isConnected", sizeof("isConnected")-1, dom_node_is_connected_read, NULL);
dom_register_prop_handler(&dom_node_prop_handlers, "ownerDocument", sizeof("ownerDocument")-1, dom_node_owner_document_read, NULL);
dom_register_prop_handler(&dom_node_prop_handlers, "namespaceURI", sizeof("namespaceURI")-1, dom_node_namespace_uri_read, NULL);
dom_register_prop_handler(&dom_node_prop_handlers, "prefix", sizeof("prefix")-1, dom_node_prefix_read, dom_node_prefix_write);
@ -660,6 +661,7 @@ PHP_MINIT_FUNCTION(dom)
dom_register_prop_handler(&dom_namespace_node_prop_handlers, "prefix", sizeof("prefix")-1, dom_node_prefix_read, NULL);
dom_register_prop_handler(&dom_namespace_node_prop_handlers, "localName", sizeof("localName")-1, dom_node_local_name_read, NULL);
dom_register_prop_handler(&dom_namespace_node_prop_handlers, "namespaceURI", sizeof("namespaceURI")-1, dom_node_namespace_uri_read, NULL);
dom_register_prop_handler(&dom_namespace_node_prop_handlers, "isConnected", sizeof("isConnected")-1, dom_node_is_connected_read, NULL);
dom_register_prop_handler(&dom_namespace_node_prop_handlers, "ownerDocument", sizeof("ownerDocument")-1, dom_node_owner_document_read, NULL);
dom_register_prop_handler(&dom_namespace_node_prop_handlers, "parentNode", sizeof("parentNode")-1, dom_node_parent_node_read, NULL);
zend_hash_add_ptr(&classes, dom_namespace_node_class_entry->name, &dom_namespace_node_prop_handlers);

View File

@ -150,7 +150,9 @@ xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr origina
void php_dom_get_content_into_zval(const xmlNode *nodep, zval *target, bool default_is_null);
zend_string *dom_node_concatenated_name_helper(size_t name_len, const char *name, size_t prefix_len, const char *prefix);
zend_string *dom_node_get_node_name_attribute_or_element(const xmlNode *nodep);
bool php_dom_is_node_connected(const xmlNode *node);
/* parentnode */
void dom_parent_node_prepend(dom_object *context, zval *nodes, uint32_t nodesc);
void dom_parent_node_append(dom_object *context, zval *nodes, uint32_t nodesc);
void dom_parent_node_after(dom_object *context, zval *nodes, uint32_t nodesc);

View File

@ -326,6 +326,9 @@ class DOMNode
/** @readonly */
public ?DOMNamedNodeMap $attributes;
/** @readonly */
public bool $isConnected;
/** @readonly */
public ?DOMDocument $ownerDocument;
@ -419,6 +422,9 @@ class DOMNameSpaceNode
/** @readonly */
public ?string $namespaceURI;
/** @readonly */
public bool $isConnected;
/** @readonly */
public ?DOMDocument $ownerDocument;

View File

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 95dc217251318b625cad5b282b1adbb8eedaf1a5 */
* Stub hash: 78e4089fa17aa0faa371917aeb48fa0dfe1819b0 */
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)
@ -1138,6 +1138,12 @@ static zend_class_entry *register_class_DOMNode(void)
zend_declare_typed_property(class_entry, property_attributes_name, &property_attributes_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_attributes_class_DOMNamedNodeMap, 0, MAY_BE_NULL));
zend_string_release(property_attributes_name);
zval property_isConnected_default_value;
ZVAL_UNDEF(&property_isConnected_default_value);
zend_string *property_isConnected_name = zend_string_init("isConnected", sizeof("isConnected") - 1, 1);
zend_declare_typed_property(class_entry, property_isConnected_name, &property_isConnected_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL));
zend_string_release(property_isConnected_name);
zval property_ownerDocument_default_value;
ZVAL_UNDEF(&property_ownerDocument_default_value);
zend_string *property_ownerDocument_name = zend_string_init("ownerDocument", sizeof("ownerDocument") - 1, 1);
@ -1222,6 +1228,12 @@ static zend_class_entry *register_class_DOMNameSpaceNode(void)
zend_declare_typed_property(class_entry, property_namespaceURI_name, &property_namespaceURI_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL));
zend_string_release(property_namespaceURI_name);
zval property_isConnected_default_value;
ZVAL_UNDEF(&property_isConnected_default_value);
zend_string *property_isConnected_name = zend_string_init("isConnected", sizeof("isConnected") - 1, 1);
zend_declare_typed_property(class_entry, property_isConnected_name, &property_isConnected_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL));
zend_string_release(property_isConnected_name);
zval property_ownerDocument_default_value;
ZVAL_UNDEF(&property_ownerDocument_default_value);
zend_string *property_ownerDocument_name = zend_string_init("ownerDocument", sizeof("ownerDocument") - 1, 1);

View File

@ -0,0 +1,59 @@
--TEST--
DOMNode::isConnected and DOMNameSpaceNode::isConnected
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$dom->loadXML('<!DOCTYPE html><html><head/><body/></html>');
$docElement = $dom->documentElement;
$head = $docElement->firstChild;
$body = $head->nextSibling;
echo "--- Created element not connected yet ---\n";
$p = $dom->createElement('p');
var_dump($p->isConnected);
echo "--- Appending and checking connection isn't broken for parents ---\n";
$body->appendChild($p);
var_dump($body->isConnected);
var_dump($p->isConnected);
$document = $docElement->parentNode;
var_dump($document->isConnected);
var_dump($dom->doctype->isConnected);
echo "--- Indirect removal should set isConnected to false for affected nodes ---\n";
$body->remove();
var_dump($p->isConnected);
var_dump($docElement->isConnected);
var_dump($body->isConnected);
var_dump($head->isConnected);
var_dump($dom->doctype->isConnected);
echo "--- Empty document test ---\n";
$dom = new DOMDocument();
var_dump($dom->isConnected);
?>
--EXPECT--
--- Created element not connected yet ---
bool(false)
--- Appending and checking connection isn't broken for parents ---
bool(true)
bool(true)
bool(true)
bool(true)
--- Indirect removal should set isConnected to false for affected nodes ---
bool(false)
bool(true)
bool(false)
bool(true)
bool(true)
--- Empty document test ---
bool(true)

View File

@ -30,7 +30,7 @@ foreach ($dataNodes AS $node) {
?>
--EXPECTF--
int(3)
object(DOMText)#%d (21) {
object(DOMText)#%d (22) {
["wholeText"]=>
string(3) "
"
@ -64,6 +64,8 @@ object(DOMText)#%d (21) {
NULL
["attributes"]=>
NULL
["isConnected"]=>
bool(false)
["ownerDocument"]=>
string(22) "(object value omitted)"
["namespaceURI"]=>
@ -78,7 +80,7 @@ object(DOMText)#%d (21) {
string(3) "
"
}
object(DOMElement)#7 (25) {
object(DOMElement)#7 (26) {
["schemaTypeInfo"]=>
NULL
["tagName"]=>
@ -121,6 +123,8 @@ object(DOMElement)#7 (25) {
NULL
["attributes"]=>
string(22) "(object value omitted)"
["isConnected"]=>
bool(false)
["ownerDocument"]=>
string(22) "(object value omitted)"
["namespaceURI"]=>
@ -138,7 +142,7 @@ object(DOMElement)#7 (25) {
Value C
"
}
object(DOMText)#%d (21) {
object(DOMText)#%d (22) {
["wholeText"]=>
string(1) "
"
@ -172,6 +176,8 @@ object(DOMText)#%d (21) {
NULL
["attributes"]=>
NULL
["isConnected"]=>
bool(false)
["ownerDocument"]=>
string(22) "(object value omitted)"
["namespaceURI"]=>

View File

@ -57,6 +57,7 @@ DOMNameSpaceNode Object
[prefix] =>
[localName] => xmlns
[namespaceURI] => http://www.sitemaps.org/schemas/sitemap/0.9
[isConnected] => 1
[ownerDocument] => (object value omitted)
[parentNode] => (object value omitted)
)
@ -73,6 +74,7 @@ DOMNameSpaceNode Object
[prefix] => xsi
[localName] => xsi
[namespaceURI] => fooooooooooooooooooooo
[isConnected] => 1
[ownerDocument] => (object value omitted)
[parentNode] => (object value omitted)
)

View File

@ -13,7 +13,7 @@ var_dump($attr);
?>
--EXPECT--
object(DOMNameSpaceNode)#3 (8) {
object(DOMNameSpaceNode)#3 (9) {
["nodeName"]=>
string(5) "xmlns"
["nodeValue"]=>
@ -26,6 +26,8 @@ object(DOMNameSpaceNode)#3 (8) {
string(5) "xmlns"
["namespaceURI"]=>
string(19) "http://php.net/test"
["isConnected"]=>
bool(true)
["ownerDocument"]=>
string(22) "(object value omitted)"
["parentNode"]=>

View File

@ -21,7 +21,7 @@ var_dump($target);
?>
--EXPECTF--
<a>barfoobaz<last/></a>
object(DOMElement)#3 (25) {
object(DOMElement)#3 (26) {
["schemaTypeInfo"]=>
NULL
["tagName"]=>
@ -60,6 +60,8 @@ object(DOMElement)#3 (25) {
NULL
["attributes"]=>
string(22) "(object value omitted)"
["isConnected"]=>
bool(true)
["ownerDocument"]=>
string(22) "(object value omitted)"
["namespaceURI"]=>
@ -74,7 +76,7 @@ object(DOMElement)#3 (25) {
string(0) ""
}
<a><last/>barfoobaz</a>
object(DOMElement)#2 (25) {
object(DOMElement)#2 (26) {
["schemaTypeInfo"]=>
NULL
["tagName"]=>
@ -113,6 +115,8 @@ object(DOMElement)#2 (25) {
string(22) "(object value omitted)"
["attributes"]=>
string(22) "(object value omitted)"
["isConnected"]=>
bool(true)
["ownerDocument"]=>
string(22) "(object value omitted)"
["namespaceURI"]=>

View File

@ -44,7 +44,7 @@ var_dump($barClone->parentNode);
?>
--EXPECT--
-- Clone DOMNameSpaceNode --
object(DOMNameSpaceNode)#3 (8) {
object(DOMNameSpaceNode)#3 (9) {
["nodeName"]=>
string(5) "xmlns"
["nodeValue"]=>
@ -57,6 +57,8 @@ object(DOMNameSpaceNode)#3 (8) {
string(5) "xmlns"
["namespaceURI"]=>
string(19) "http://php.net/test"
["isConnected"]=>
bool(true)
["ownerDocument"]=>
string(22) "(object value omitted)"
["parentNode"]=>

View File

@ -54,6 +54,7 @@ DOMDocument Object
[previousSibling] =>
[nextSibling] =>
[attributes] =>
[isConnected] => 1
[ownerDocument] =>
[namespaceURI] =>
[prefix] =>

View File

@ -17,7 +17,7 @@ var_dump($nodes->item(0));
?>
--EXPECT--
object(DOMNameSpaceNode)#4 (8) {
object(DOMNameSpaceNode)#4 (9) {
["nodeName"]=>
string(9) "xmlns:xml"
["nodeValue"]=>
@ -30,6 +30,8 @@ object(DOMNameSpaceNode)#4 (8) {
string(3) "xml"
["namespaceURI"]=>
string(36) "http://www.w3.org/XML/1998/namespace"
["isConnected"]=>
bool(true)
["ownerDocument"]=>
string(22) "(object value omitted)"
["parentNode"]=>