diff --git a/ext/dom/attr.c b/ext/dom/attr.c index e48d124976c25..24c92e2a33ab1 100644 --- a/ext/dom/attr.c +++ b/ext/dom/attr.c @@ -199,4 +199,45 @@ PHP_METHOD(DOMAttr, isId) } /* }}} end dom_attr_is_id */ +xmlChar *dom_attr_value(const xmlAttr *attr, bool *free) +{ + /* For attributes we can have an optimized fast-path. + * This fast-path is only possible in the (common) case where the attribute + * has a single text child. Note that if the child or the content is NULL, this + * is equivalent to not having content (i.e. the attribute has the empty string as value). */ + + *free = false; + + if (attr->children == NULL) { + return BAD_CAST ""; + } + + if (attr->children->type == XML_TEXT_NODE && attr->children->next == NULL) { + if (attr->children->content == NULL) { + return BAD_CAST ""; + } else { + return attr->children->content; + } + } + + xmlChar *value = xmlNodeGetContent((const xmlNode *) attr); + if (UNEXPECTED(value == NULL)) { + return BAD_CAST ""; + } + + *free = true; + return value; +} + +bool dom_compare_value(const xmlAttr *attr, const xmlChar *value) +{ + bool free; + xmlChar *attr_value = dom_attr_value(attr, &free); + bool result = xmlStrEqual(attr_value, value); + if (free) { + xmlFree(attr_value); + } + return result; +} + #endif diff --git a/ext/dom/config.m4 b/ext/dom/config.m4 index b3a92287b3f1c..4dcde6105a583 100644 --- a/ext/dom/config.m4 +++ b/ext/dom/config.m4 @@ -32,7 +32,7 @@ if test "$PHP_DOM" != "no"; then documentfragment.c domimplementation.c \ element.c node.c characterdata.c \ documenttype.c entity.c \ - nodelist.c text.c comment.c \ + nodelist.c html_collection.c text.c comment.c \ entityreference.c \ notation.c xpath.c dom_iterators.c \ namednodemap.c xpath_callbacks.c \ diff --git a/ext/dom/config.w32 b/ext/dom/config.w32 index a70d226d0fa31..1a5d33bf7ca4f 100644 --- a/ext/dom/config.w32 +++ b/ext/dom/config.w32 @@ -12,7 +12,7 @@ if (PHP_DOM == "yes") { domexception.c parentnode.c processinginstruction.c \ cdatasection.c documentfragment.c domimplementation.c element.c \ node.c characterdata.c documenttype.c \ - entity.c nodelist.c text.c comment.c \ + entity.c nodelist.c html_collection.c text.c comment.c \ entityreference.c \ notation.c xpath.c dom_iterators.c \ namednodemap.c xpath_callbacks.c", null, "-Iext/dom/lexbor"); diff --git a/ext/dom/html_collection.c b/ext/dom/html_collection.c new file mode 100644 index 0000000000000..3b74074559a26 --- /dev/null +++ b/ext/dom/html_collection.c @@ -0,0 +1,143 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Niels Dossche | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#if defined(HAVE_LIBXML) && defined(HAVE_DOM) +#include "php_dom.h" +#include "nodelist.h" +#include "html_collection.h" +#include "namespace_compat.h" + +typedef struct _dom_named_item { + dom_object *context_intern; + xmlNodePtr node; +} dom_named_item; + +/* https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem-key */ +static dom_named_item dom_html_collection_named_item(zend_string *key, zend_object *zobj) +{ + dom_named_item ret = {NULL, NULL}; + + /* 1. If key is the empty string, return null. */ + if (ZSTR_LEN(key) == 0) { + return ret; + } + + dom_object *intern = php_dom_obj_from_obj(zobj); + dom_nnodemap_object *objmap = intern->ptr; + + /* 2. Return the first element in the collection for which at least one of the following is true: */ + xmlNodePtr basep = dom_object_get_node(objmap->baseobj); + if (basep != NULL) { + int cur = 0; + int next = cur; /* not +1, otherwise we skip the first candidate */ + xmlNodePtr candidate = basep->children; + while (candidate != NULL) { + candidate = dom_get_elements_by_tag_name_ns_raw(basep, candidate, objmap->ns, objmap->local, objmap->local_lower, &cur, next); + if (candidate == NULL) { + break; + } + + xmlAttrPtr attr; + + /* it has an ID which is key; */ + if ((attr = xmlHasNsProp(candidate, BAD_CAST "id", NULL)) != NULL && dom_compare_value(attr, BAD_CAST ZSTR_VAL(key))) { + ret.context_intern = objmap->baseobj; + ret.node = candidate; + return ret; + } + /* it is in the HTML namespace and has a name attribute whose value is key; */ + else if (php_dom_ns_is_fast(candidate, php_dom_ns_is_html_magic_token)) { + if ((attr = xmlHasNsProp(candidate, BAD_CAST "name", NULL)) != NULL && dom_compare_value(attr, BAD_CAST ZSTR_VAL(key))) { + ret.context_intern = objmap->baseobj; + ret.node = candidate; + return ret; + } + } + + next = cur + 1; + } + } + + return ret; +} + +static void dom_html_collection_named_item_into_zval(zval *return_value, zend_string *key, zend_object *zobj) +{ + dom_named_item named_item = dom_html_collection_named_item(key, zobj); + if (named_item.node != NULL) { + DOM_RET_OBJ(named_item.node, named_item.context_intern); + } else { + RETURN_NULL(); + } +} + +PHP_METHOD(DOM_HTMLCollection, namedItem) +{ + zend_string *key; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(key) + ZEND_PARSE_PARAMETERS_END(); + dom_html_collection_named_item_into_zval(return_value, key, Z_OBJ_P(ZEND_THIS)); +} + +zval *dom_html_collection_read_dimension(zend_object *object, zval *offset, int type, zval *rv) +{ + if (UNEXPECTED(!offset)) { + zend_throw_error(NULL, "Cannot append to %s", ZSTR_VAL(object->ce->name)); + return NULL; + } + + dom_nodelist_dimension_index index = dom_modern_nodelist_get_index(offset); + if (UNEXPECTED(index.type == DOM_NODELIST_DIM_ILLEGAL)) { + zend_illegal_container_offset(object->ce->name, offset, type); + return NULL; + } + + if (index.type == DOM_NODELIST_DIM_STRING) { + dom_html_collection_named_item_into_zval(rv, index.str, object); + } else { + ZEND_ASSERT(index.type == DOM_NODELIST_DIM_LONG); + php_dom_nodelist_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, index.lval, rv); + } + + return rv; +} + +int dom_html_collection_has_dimension(zend_object *object, zval *member, int check_empty) +{ + /* If it exists, it cannot be empty because nodes aren't empty. */ + ZEND_IGNORE_VALUE(check_empty); + + dom_nodelist_dimension_index index = dom_modern_nodelist_get_index(member); + if (UNEXPECTED(index.type == DOM_NODELIST_DIM_ILLEGAL)) { + zend_illegal_container_offset(object->ce->name, member, BP_VAR_IS); + return 0; + } + + if (index.type == DOM_NODELIST_DIM_STRING) { + return dom_html_collection_named_item(index.str, object).node != NULL; + } else { + ZEND_ASSERT(index.type == DOM_NODELIST_DIM_LONG); + return index.lval >= 0 && index.lval < php_dom_get_nodelist_length(php_dom_obj_from_obj(object)); + } +} + +#endif diff --git a/ext/dom/html_collection.h b/ext/dom/html_collection.h new file mode 100644 index 0000000000000..a94daa1aae805 --- /dev/null +++ b/ext/dom/html_collection.h @@ -0,0 +1,23 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Niels Dossche | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_HTML_COLLECTION_H +#define PHP_HTML_COLLECTION_H + +zval *dom_html_collection_read_dimension(zend_object *object, zval *offset, int type, zval *rv); +int dom_html_collection_has_dimension(zend_object *object, zval *member, int check_empty); + +#endif diff --git a/ext/dom/nodelist.c b/ext/dom/nodelist.c index 0a0ab32e5c20e..b615705b621f8 100644 --- a/ext/dom/nodelist.c +++ b/ext/dom/nodelist.c @@ -22,6 +22,7 @@ #include "php.h" #if defined(HAVE_LIBXML) && defined(HAVE_DOM) #include "php_dom.h" +#include "nodelist.h" #include "zend_interfaces.h" /* @@ -249,4 +250,63 @@ ZEND_METHOD(DOMNodeList, getIterator) zend_create_internal_iterator_zval(return_value, ZEND_THIS); } +dom_nodelist_dimension_index dom_modern_nodelist_get_index(const zval *offset) +{ + dom_nodelist_dimension_index ret; + + ZVAL_DEREF(offset); + + if (Z_TYPE_P(offset) == IS_LONG) { + ret.type = DOM_NODELIST_DIM_LONG; + ret.lval = Z_LVAL_P(offset); + } else if (Z_TYPE_P(offset) == IS_DOUBLE) { + ret.type = DOM_NODELIST_DIM_LONG; + ret.lval = zend_dval_to_lval_safe(Z_DVAL_P(offset)); + } else if (Z_TYPE_P(offset) == IS_STRING) { + zend_ulong lval; + if (ZEND_HANDLE_NUMERIC(Z_STR_P(offset), lval)) { + ret.type = DOM_NODELIST_DIM_LONG; + ret.lval = (zend_long) lval; + } else { + ret.type = DOM_NODELIST_DIM_STRING; + ret.str = Z_STR_P(offset); + } + } else { + ret.type = DOM_NODELIST_DIM_ILLEGAL; + } + + return ret; +} + +zval *dom_modern_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv) +{ + if (UNEXPECTED(!offset)) { + zend_throw_error(NULL, "Cannot append to %s", ZSTR_VAL(object->ce->name)); + return NULL; + } + + dom_nodelist_dimension_index index = dom_modern_nodelist_get_index(offset); + if (UNEXPECTED(index.type == DOM_NODELIST_DIM_ILLEGAL || index.type == DOM_NODELIST_DIM_STRING)) { + zend_illegal_container_offset(object->ce->name, offset, type); + return NULL; + } + + php_dom_nodelist_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, index.lval, rv); + return rv; +} + +int dom_modern_nodelist_has_dimension(zend_object *object, zval *member, int check_empty) +{ + /* If it exists, it cannot be empty because nodes aren't empty. */ + ZEND_IGNORE_VALUE(check_empty); + + dom_nodelist_dimension_index index = dom_modern_nodelist_get_index(member); + if (UNEXPECTED(index.type == DOM_NODELIST_DIM_ILLEGAL || index.type == DOM_NODELIST_DIM_STRING)) { + zend_illegal_container_offset(object->ce->name, member, BP_VAR_IS); + return 0; + } + + return index.lval >= 0 && index.lval < php_dom_get_nodelist_length(php_dom_obj_from_obj(object)); +} + #endif diff --git a/ext/dom/nodelist.h b/ext/dom/nodelist.h new file mode 100644 index 0000000000000..72264d683f338 --- /dev/null +++ b/ext/dom/nodelist.h @@ -0,0 +1,40 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Niels Dossche | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_NODELIST_H +#define PHP_NODELIST_H + +enum dom_nodelist_dimension_index_type { + DOM_NODELIST_DIM_ILLEGAL, + DOM_NODELIST_DIM_STRING, + DOM_NODELIST_DIM_LONG, +}; + +typedef struct _dom_nodelist_dimension_index { + union { + zend_long lval; + zend_string *str; + }; + enum dom_nodelist_dimension_index_type type; +} dom_nodelist_dimension_index; + +void php_dom_nodelist_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value); +int php_dom_get_nodelist_length(dom_object *obj); +dom_nodelist_dimension_index dom_modern_nodelist_get_index(const zval *offset); +zval *dom_modern_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv); +int dom_modern_nodelist_has_dimension(zend_object *object, zval *member, int check_empty); + +#endif diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index dce9a0fb490c8..978fbe96dec01 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -23,6 +23,8 @@ #include "php.h" #if defined(HAVE_LIBXML) && defined(HAVE_DOM) #include "php_dom.h" +#include "nodelist.h" +#include "html_collection.h" #include "namespace_compat.h" #include "internal_helpers.h" #include "php_dom_arginfo.h" @@ -89,6 +91,7 @@ static zend_object_handlers dom_nnodemap_object_handlers; static zend_object_handlers dom_nodelist_object_handlers; static zend_object_handlers dom_modern_nnodemap_object_handlers; static zend_object_handlers dom_modern_nodelist_object_handlers; +static zend_object_handlers dom_html_collection_object_handlers; static zend_object_handlers dom_object_namespace_node_handlers; static zend_object_handlers dom_modern_domimplementation_object_handlers; #ifdef LIBXML_XPATH_ENABLED @@ -656,8 +659,6 @@ static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int t static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty); static zval *dom_modern_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv); static int dom_modern_nodemap_has_dimension(zend_object *object, zval *member, int check_empty); -static zval *dom_modern_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv); -static int dom_modern_nodelist_has_dimension(zend_object *object, zval *member, int check_empty); static zend_object *dom_objects_store_clone_obj(zend_object *zobject); #ifdef LIBXML_XPATH_ENABLED @@ -716,6 +717,10 @@ PHP_MINIT_FUNCTION(dom) dom_modern_nodelist_object_handlers.read_dimension = dom_modern_nodelist_read_dimension; dom_modern_nodelist_object_handlers.has_dimension = dom_modern_nodelist_has_dimension; + memcpy(&dom_html_collection_object_handlers, &dom_modern_nodelist_object_handlers, sizeof(zend_object_handlers)); + dom_html_collection_object_handlers.read_dimension = dom_html_collection_read_dimension; + dom_html_collection_object_handlers.has_dimension = dom_html_collection_has_dimension; + memcpy(&dom_object_namespace_node_handlers, &dom_object_handlers, sizeof(zend_object_handlers)); dom_object_namespace_node_handlers.offset = XtOffsetOf(dom_object_namespace_node, dom.std); dom_object_namespace_node_handlers.free_obj = dom_object_namespace_node_free_storage; @@ -928,7 +933,7 @@ PHP_MINIT_FUNCTION(dom) dom_html_collection_class_entry = register_class_DOM_HTMLCollection(zend_ce_aggregate, zend_ce_countable); dom_html_collection_class_entry->create_object = dom_nnodemap_objects_new; - dom_html_collection_class_entry->default_object_handlers = &dom_modern_nodelist_object_handlers; + dom_html_collection_class_entry->default_object_handlers = &dom_html_collection_object_handlers; dom_html_collection_class_entry->get_iterator = php_dom_get_iterator; zend_hash_add_new_ptr(&classes, dom_html_collection_class_entry->name, &dom_nodelist_prop_handlers); @@ -2193,58 +2198,6 @@ static int dom_nodelist_has_dimension(zend_object *object, zval *member, int che return offset >= 0 && offset < php_dom_get_nodelist_length(php_dom_obj_from_obj(object)); } -static zend_long dom_modern_nodelist_get_index(zval *offset, bool *failed) -{ - zend_ulong lval; - ZVAL_DEREF(offset); - if (Z_TYPE_P(offset) == IS_LONG) { - *failed = false; - return Z_LVAL_P(offset); - } else if (Z_TYPE_P(offset) == IS_DOUBLE) { - *failed = false; - return zend_dval_to_lval_safe(Z_DVAL_P(offset)); - } else if (Z_TYPE_P(offset) == IS_STRING && ZEND_HANDLE_NUMERIC(Z_STR_P(offset), lval)) { - *failed = false; - return (zend_long) lval; - } else { - *failed = true; - return 0; - } -} - -static zval *dom_modern_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv) -{ - if (UNEXPECTED(!offset)) { - zend_throw_error(NULL, "Cannot append to %s", ZSTR_VAL(object->ce->name)); - return NULL; - } - - bool failed; - zend_long lval = dom_modern_nodelist_get_index(offset, &failed); - if (UNEXPECTED(failed)) { - zend_illegal_container_offset(object->ce->name, offset, type); - return NULL; - } - - php_dom_nodelist_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, lval, rv); - return rv; -} - -static int dom_modern_nodelist_has_dimension(zend_object *object, zval *member, int check_empty) -{ - /* If it exists, it cannot be empty because nodes aren't empty. */ - ZEND_IGNORE_VALUE(check_empty); - - bool failed; - zend_long lval = dom_modern_nodelist_get_index(member, &failed); - if (UNEXPECTED(failed)) { - zend_illegal_container_offset(object->ce->name, member, BP_VAR_IS); - return 0; - } - - return lval >= 0 && lval < php_dom_get_nodelist_length(php_dom_obj_from_obj(object)); -} - void dom_remove_all_children(xmlNodePtr nodep) { if (nodep->children) { @@ -2273,24 +2226,13 @@ void php_dom_get_content_into_zval(const xmlNode *nodep, zval *return_value, boo } case XML_ATTRIBUTE_NODE: { - /* For attributes we can also have an optimized fast-path. - * This fast-path is only possible in the (common) case where the attribute - * has a single text child. Note that if the child or the content is NULL, this - * is equivalent to not having content (i.e. the attribute has the empty string as value). */ - - if (nodep->children == NULL) { - RETURN_EMPTY_STRING(); + bool free; + xmlChar *value = dom_attr_value((const xmlAttr *) nodep, &free); + RETURN_STRING_FAST((const char *) value); + if (free) { + xmlFree(value); } - - if (nodep->children->type == XML_TEXT_NODE && nodep->children->next == NULL) { - if (nodep->children->content == NULL) { - RETURN_EMPTY_STRING(); - } else { - RETURN_STRING((const char *) nodep->children->content); - } - } - - ZEND_FALLTHROUGH; + return; } default: { diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h index e64695f07b1f1..29e390a096413 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -174,6 +174,9 @@ void dom_document_convert_to_modern(php_libxml_ref_obj *document, xmlDocPtr lxml dom_object *php_dom_instantiate_object_helper(zval *return_value, zend_class_entry *ce, xmlNodePtr obj, dom_object *parent); xmlDocPtr php_dom_create_html_doc(void); +xmlChar *dom_attr_value(const xmlAttr *attr, bool *free); +bool dom_compare_value(const xmlAttr *attr, const xmlChar *value); + typedef enum { DOM_LOAD_STRING = 0, DOM_LOAD_FILE = 1, @@ -203,9 +206,7 @@ xmlNodePtr php_dom_named_node_map_get_named_item(dom_nnodemap_object *objmap, co void php_dom_named_node_map_get_named_item_into_zval(dom_nnodemap_object *objmap, const zend_string *named, zval *return_value); xmlNodePtr php_dom_named_node_map_get_item(dom_nnodemap_object *objmap, zend_long index); void php_dom_named_node_map_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value); -void php_dom_nodelist_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value); int php_dom_get_namednodemap_length(dom_object *obj); -int php_dom_get_nodelist_length(dom_object *obj); xmlNodePtr dom_clone_node(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr node, xmlDocPtr doc, bool recursive); diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index 196a858817572..d744a94349727 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -1279,7 +1279,7 @@ class HTMLCollection implements \IteratorAggregate, \Countable /** @implementation-alias DOMNodeList::item */ public function item(int $index): ?Element {} - /* TODO: implement namedItem */ + public function namedItem(string $key): ?Element {} /** @implementation-alias DOMNodeList::count */ public function count(): int {} diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index a386d5217dc9b..5e759686bc6c3 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: f441c789fdce91e8fc71f450b294c11059999af1 */ + * Stub hash: 37a1c811bfc8c611d686f0842d06fc327b54511f */ 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) @@ -721,6 +721,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_DOM_HTMLCollection_item, 0, ZEND_ARG_TYPE_INFO(0, index, IS_LONG, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_DOM_HTMLCollection_namedItem, 0, 1, DOM\\Element, 1) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_DOM_HTMLCollection_count arginfo_class_DOM_Node_getLineNo #define arginfo_class_DOM_HTMLCollection_getIterator arginfo_class_DOMNodeList_getIterator @@ -1256,6 +1260,7 @@ ZEND_METHOD(DOM_Node, appendChild); ZEND_METHOD(DOM_Node, replaceChild); ZEND_METHOD(DOM_Node, removeChild); ZEND_METHOD(DOM_Node, getNodePath); +ZEND_METHOD(DOM_HTMLCollection, namedItem); ZEND_METHOD(DOM_Element, removeAttribute); ZEND_METHOD(DOM_Element, setAttributeNodeNS); ZEND_METHOD(DOM_Element, removeAttributeNode); @@ -1620,6 +1625,7 @@ static const zend_function_entry class_DOM_DTDNamedNodeMap_methods[] = { static const zend_function_entry class_DOM_HTMLCollection_methods[] = { ZEND_RAW_FENTRY("item", zim_DOMNodeList_item, arginfo_class_DOM_HTMLCollection_item, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_ME(DOM_HTMLCollection, namedItem, arginfo_class_DOM_HTMLCollection_namedItem, ZEND_ACC_PUBLIC) ZEND_RAW_FENTRY("count", zim_DOMNodeList_count, arginfo_class_DOM_HTMLCollection_count, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getIterator", zim_DOMNodeList_getIterator, arginfo_class_DOM_HTMLCollection_getIterator, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_FE_END diff --git a/ext/dom/tests/modern/html/interactions/HTMLCollection_dimension_errors.phpt b/ext/dom/tests/modern/html/interactions/HTMLCollection_dimension_errors.phpt new file mode 100644 index 0000000000000..e68d1ede3863c --- /dev/null +++ b/ext/dom/tests/modern/html/interactions/HTMLCollection_dimension_errors.phpt @@ -0,0 +1,32 @@ +--TEST-- +HTMLCollection::namedItem() and dimension handling for named accesses +--EXTENSIONS-- +dom +--FILE-- +'); + +try { + $dom->getElementsByTagName('root')[][1] = 1; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +try { + $dom->getElementsByTagName('root')[true]; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +try { + isset($dom->getElementsByTagName('root')[true]); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Cannot append to DOM\HTMLCollection +Cannot access offset of type bool on DOM\HTMLCollection +Cannot access offset of type bool in isset or empty diff --git a/ext/dom/tests/modern/html/interactions/HTMLCollection_named_reads.phpt b/ext/dom/tests/modern/html/interactions/HTMLCollection_named_reads.phpt new file mode 100644 index 0000000000000..82883e4ceec03 --- /dev/null +++ b/ext/dom/tests/modern/html/interactions/HTMLCollection_named_reads.phpt @@ -0,0 +1,82 @@ +--TEST-- +HTMLCollection::namedItem() and dimension handling for named accesses +--EXTENSIONS-- +dom +--FILE-- + +]> + + 1 + 2 + 2 with entity + 3 + 4 + 5 + without html ns + with html ns + +XML; + +$dom = DOM\XMLDocument::createFromString($xml); + +function test($obj, $name) { + echo "--- Query \"$name\" ---\n"; + var_dump($obj->namedItem($name)?->textContent); + var_dump($obj[$name]?->textContent); + var_dump(isset($obj[$name])); + + // Search to check for dimension access consistency + $node = $obj[$name]; + if ($node) { + $found = false; + for ($i = 0; $i < $obj->length && !$found; $i++) { + $found = $obj[$i] === $node; + } + if (!$found) { + throw new Error('inconsistency in dimension access'); + } + } +} + +test($dom->getElementsByTagName('node'), 'foo'); +test($dom->getElementsByTagName('node'), ''); +test($dom->getElementsByTagName('node'), 'does not exist'); +test($dom->getElementsByTagName('node'), 'wrong'); +test($dom->getElementsByTagName('node'), 'bar'); +test($dom->getElementsByTagName('x'), 'foo'); +test($dom->getElementsByTagName('x'), 'footest'); + +?> +--EXPECT-- +--- Query "foo" --- +string(1) "5" +string(1) "5" +bool(true) +--- Query "" --- +NULL +NULL +bool(false) +--- Query "does not exist" --- +NULL +NULL +bool(false) +--- Query "wrong" --- +string(1) "4" +string(1) "4" +bool(true) +--- Query "bar" --- +string(12) "with html ns" +string(12) "with html ns" +bool(true) +--- Query "foo" --- +string(1) "2" +string(1) "2" +bool(true) +--- Query "footest" --- +string(13) "2 with entity" +string(13) "2 with entity" +bool(true)