From b24b3510f946885cd32ce8fc46fc42e035679eca Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 12 Jul 2023 20:14:14 +0200 Subject: [PATCH] Implement DOMElement::className ref: https://dom.spec.whatwg.org/#dom-element-classname Closes GH-11691. --- NEWS | 1 + UPGRADING | 2 + ext/dom/dom_properties.h | 2 + ext/dom/element.c | 49 +++++++++++++++++++++++++ ext/dom/php_dom.c | 1 + ext/dom/php_dom.stub.php | 2 + ext/dom/php_dom_arginfo.h | 8 +++- ext/dom/tests/DOMElement_className.phpt | 47 ++++++++++++++++++++++++ ext/dom/tests/bug69846.phpt | 4 +- ext/dom/tests/bug80602_3.phpt | 8 +++- 10 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 ext/dom/tests/DOMElement_className.phpt diff --git a/NEWS b/NEWS index 78881134a0a83..91cdacbe186b3 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,7 @@ PHP NEWS . Added DOMNode::contains() and DOMNameSpaceNode::contains(). (nielsdos) . Added DOMElement::getAttributeNames(). (nielsdos) . Added DOMNode::getRootNode(). (nielsdos) + . Added DOMElement::className. (nielsdos) - Intl: . Fix memory leak in MessageFormatter::format() on failure. (Girgias) diff --git a/UPGRADING b/UPGRADING index aced261f021d9..cbe3ce143c128 100644 --- a/UPGRADING +++ b/UPGRADING @@ -252,6 +252,8 @@ PHP 8.3 UPGRADE NOTES . Added DOMNode::getRootNode(). The $options argument does nothing at the moment because it only influences the shadow DOM, which we do not support yet. + . Added DOMElement::className. This is not binary-safe at the moment + because of underlying limitations of libxml2. - JSON: . Added json_validate(), which returns whether the json is valid for diff --git a/ext/dom/dom_properties.h b/ext/dom/dom_properties.h index 32f2419323833..147c38ea3339d 100644 --- a/ext/dom/dom_properties.h +++ b/ext/dom/dom_properties.h @@ -71,6 +71,8 @@ int dom_documenttype_internal_subset_read(dom_object *obj, zval *retval); /* element properties */ int dom_element_tag_name_read(dom_object *obj, zval *retval); +int dom_element_class_name_read(dom_object *obj, zval *retval); +int dom_element_class_name_write(dom_object *obj, zval *newval); int dom_element_schema_type_info_read(dom_object *obj, zval *retval); /* entity properties */ diff --git a/ext/dom/element.c b/ext/dom/element.c index 5df99a7b66bd1..2ca3ef2f06692 100644 --- a/ext/dom/element.c +++ b/ext/dom/element.c @@ -137,6 +137,55 @@ int dom_element_tag_name_read(dom_object *obj, zval *retval) /* }}} */ +/* {{{ className string +URL: https://dom.spec.whatwg.org/#dom-element-classname +Since: +*/ +int dom_element_class_name_read(dom_object *obj, zval *retval) +{ + xmlNodePtr nodep = dom_object_get_node(obj); + + if (nodep == NULL) { + php_dom_throw_error(INVALID_STATE_ERR, 1); + return FAILURE; + } + + xmlChar *content = xmlGetNoNsProp(nodep, (const xmlChar *) "class"); + if (content == NULL) { + ZVAL_EMPTY_STRING(retval); + return SUCCESS; + } + + ZVAL_STRING(retval, (const char *) content); + xmlFree(content); + + return SUCCESS; +} + +int dom_element_class_name_write(dom_object *obj, zval *newval) +{ + xmlNode *nodep = dom_object_get_node(obj); + + if (nodep == NULL) { + php_dom_throw_error(INVALID_STATE_ERR, 1); + return FAILURE; + } + + if (dom_node_is_read_only(nodep) == SUCCESS) { + php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, dom_get_strict_error(obj->document)); + return FAILURE; + } + + /* Typed property, so it is a string already */ + ZEND_ASSERT(Z_TYPE_P(newval) == IS_STRING); + xmlSetProp(nodep, (const xmlChar *) "class", (const xmlChar *) Z_STRVAL_P(newval)); + + php_libxml_invalidate_node_list_cache_from_doc(nodep->doc); + + return SUCCESS; +} +/* }}} */ + /* {{{ schemaTypeInfo typeinfo readonly=yes URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Element-schemaTypeInfo diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 453b4d95ecc91..2684c00e98d9c 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -751,6 +751,7 @@ PHP_MINIT_FUNCTION(dom) zend_hash_init(&dom_element_prop_handlers, 0, NULL, dom_dtor_prop_handler, 1); dom_register_prop_handler(&dom_element_prop_handlers, "tagName", sizeof("tagName")-1, dom_element_tag_name_read, NULL); + dom_register_prop_handler(&dom_element_prop_handlers, "className", sizeof("className")-1, dom_element_class_name_read, dom_element_class_name_write); dom_register_prop_handler(&dom_element_prop_handlers, "schemaTypeInfo", sizeof("schemaTypeInfo")-1, dom_element_schema_type_info_read, NULL); dom_register_prop_handler(&dom_element_prop_handlers, "firstElementChild", sizeof("firstElementChild")-1, dom_parent_node_first_element_child_read, NULL); dom_register_prop_handler(&dom_element_prop_handlers, "lastElementChild", sizeof("lastElementChild")-1, dom_parent_node_last_element_child_read, NULL); diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index 5ff5f2528ead8..97ce8f487354c 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -542,6 +542,8 @@ class DOMElement extends DOMNode implements DOMParentNode, DOMChildNode /** @readonly */ public string $tagName; + public string $className; + /** @readonly */ public mixed $schemaTypeInfo = null; diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index d3b63377466d0..9f39a731e292a 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: bba8006906191fa0dd961d65460749a7c7f12afc */ + * Stub hash: 91732c51635f43f016f4b531d9aa8e00312084ec */ 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) @@ -1374,6 +1374,12 @@ static zend_class_entry *register_class_DOMElement(zend_class_entry *class_entry zend_declare_typed_property(class_entry, property_tagName_name, &property_tagName_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_tagName_name); + zval property_className_default_value; + ZVAL_UNDEF(&property_className_default_value); + zend_string *property_className_name = zend_string_init("className", sizeof("className") - 1, 1); + zend_declare_typed_property(class_entry, property_className_name, &property_className_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release(property_className_name); + zval property_schemaTypeInfo_default_value; ZVAL_NULL(&property_schemaTypeInfo_default_value); zend_string *property_schemaTypeInfo_name = zend_string_init("schemaTypeInfo", sizeof("schemaTypeInfo") - 1, 1); diff --git a/ext/dom/tests/DOMElement_className.phpt b/ext/dom/tests/DOMElement_className.phpt new file mode 100644 index 0000000000000..fb19f0e23021f --- /dev/null +++ b/ext/dom/tests/DOMElement_className.phpt @@ -0,0 +1,47 @@ +--TEST-- +DOMElement::className +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); + +var_dump($dom->documentElement->className); +$dom->documentElement->className = "hello & world<>"; +var_dump($dom->documentElement->className); +$dom->documentElement->className = ""; +var_dump($dom->documentElement->className); +$dom->documentElement->className = "é"; +var_dump($dom->documentElement->className); +$dom->documentElement->className = "\0"; +var_dump($dom->documentElement->className); +$dom->documentElement->className = 12345; +var_dump($dom->documentElement->className); +try { + $dom->documentElement->className = new MyStringable(); +} catch (Throwable $e) { + echo "Error: ", $e->getMessage(), "\n"; +} +var_dump($dom->documentElement->className); +echo $dom->saveXML(); + +?> +--EXPECT-- +string(0) "" +string(15) "hello & world<>" +string(0) "" +string(2) "é" +string(0) "" +string(5) "12345" +Error: foo +string(5) "12345" + + diff --git a/ext/dom/tests/bug69846.phpt b/ext/dom/tests/bug69846.phpt index 7655d203fa22f..24d7526a7962b 100644 --- a/ext/dom/tests/bug69846.phpt +++ b/ext/dom/tests/bug69846.phpt @@ -78,11 +78,13 @@ object(DOMText)#%d (21) { string(3) " " } -object(DOMElement)#7 (23) { +object(DOMElement)#7 (24) { ["schemaTypeInfo"]=> NULL ["tagName"]=> string(5) "form1" + ["className"]=> + string(0) "" ["firstElementChild"]=> string(22) "(object value omitted)" ["lastElementChild"]=> diff --git a/ext/dom/tests/bug80602_3.phpt b/ext/dom/tests/bug80602_3.phpt index f9bf67e778da5..195952fdc7b32 100644 --- a/ext/dom/tests/bug80602_3.phpt +++ b/ext/dom/tests/bug80602_3.phpt @@ -21,11 +21,13 @@ var_dump($target); ?> --EXPECTF-- barfoobaz -object(DOMElement)#3 (23) { +object(DOMElement)#3 (24) { ["schemaTypeInfo"]=> NULL ["tagName"]=> string(4) "last" + ["className"]=> + string(0) "" ["firstElementChild"]=> NULL ["lastElementChild"]=> @@ -70,11 +72,13 @@ object(DOMElement)#3 (23) { string(0) "" } barfoobaz -object(DOMElement)#2 (23) { +object(DOMElement)#2 (24) { ["schemaTypeInfo"]=> NULL ["tagName"]=> string(4) "last" + ["className"]=> + string(0) "" ["firstElementChild"]=> NULL ["lastElementChild"]=>