diff --git a/ext/dom/attr.c b/ext/dom/attr.c index 746ab4efa36..24c92e2a33a 100644 --- a/ext/dom/attr.c +++ b/ext/dom/attr.c @@ -229,4 +229,15 @@ xmlChar *dom_attr_value(const xmlAttr *attr, bool *free) 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 b3a92287b3f..4dcde6105a5 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 a70d226d0fa..1a5d33bf7ca 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 00000000000..dfcc304e8c7 --- /dev/null +++ b/ext/dom/html_collection.c @@ -0,0 +1,74 @@ +/* + +----------------------------------------------------------------------+ + | 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 "namespace_compat.h" + +/* https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem-key */ +PHP_METHOD(DOM_HTMLCollection, namedItem) +{ + zend_string *key; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_PATH_STR(key) + ZEND_PARSE_PARAMETERS_END(); + + /* 1. If key is the empty string, return null. */ + if (ZSTR_LEN(key) == 0) { + RETURN_NULL(); + } + + dom_object *intern = Z_DOMOBJ_P(ZEND_THIS); + 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; + 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))) { + DOM_RET_OBJ(candidate, objmap->baseobj); + return; + } + /* 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))) { + DOM_RET_OBJ(candidate, objmap->baseobj); + return; + } + } + + next = cur + 1; + } + } +} + +#endif diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h index 0cd62a1afce..a51b5a9cb99 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -175,6 +175,7 @@ dom_object *php_dom_instantiate_object_helper(zval *return_value, zend_class_ent 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, diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index 196a8588175..d744a943497 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -1279,7 +1279,7 @@ namespace DOM /** @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 a386d5217dc..5e759686bc6 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_named_reads.phpt b/ext/dom/tests/modern/html/interactions/HTMLCollection_named_reads.phpt new file mode 100644 index 00000000000..708f618d1a9 --- /dev/null +++ b/ext/dom/tests/modern/html/interactions/HTMLCollection_named_reads.phpt @@ -0,0 +1,41 @@ +--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); +var_dump($dom->getElementsByTagName('node')->namedItem('foo')?->textContent); +var_dump($dom->getElementsByTagName('node')->namedItem('')?->textContent); +var_dump($dom->getElementsByTagName('node')->namedItem('does not exist')?->textContent); +var_dump($dom->getElementsByTagName('node')->namedItem('wrong')?->textContent); +var_dump($dom->getElementsByTagName('node')->namedItem('bar')?->textContent); +var_dump($dom->getElementsByTagName('x')->namedItem('foo')?->textContent); +var_dump($dom->getElementsByTagName('x')->namedItem('footest')?->textContent); + +?> +--EXPECT-- +string(1) "5" +NULL +NULL +string(1) "4" +string(12) "with html ns" +string(1) "2" +string(13) "2 with entity"