1
+3
4
- +From fca2bbb90e07a6d90ce1144697d76589ac8d5409 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 11 Jul 2025 23:03:22 +0200 Subject: [PATCH 1/6] Implement GH-18550: Implement getElementsByClassName() Spec: https://dom.spec.whatwg.org/#ref-for-dom-element-getelementsbyclassname --- ext/dom/element.c | 38 ++++++++ ext/dom/obj_map.c | 97 ++++++++++++++++++- ext/dom/obj_map.h | 1 + ext/dom/php_dom.stub.php | 3 + ext/dom/php_dom_arginfo.h | 11 ++- .../Element_getElementsByClassName.phpt | 42 ++++++++ .../Element_getElementsByClassName_empty.phpt | 32 ++++++ ext/dom/token_list.c | 54 ++++++++++- ext/dom/token_list.h | 2 + 9 files changed, 276 insertions(+), 4 deletions(-) create mode 100644 ext/dom/tests/modern/common/Element_getElementsByClassName.phpt create mode 100644 ext/dom/tests/modern/common/Element_getElementsByClassName_empty.phpt diff --git a/ext/dom/element.c b/ext/dom/element.c index ea18d0a817ede..f0afcf07eb2fe 100644 --- a/ext/dom/element.c +++ b/ext/dom/element.c @@ -842,6 +842,44 @@ PHP_METHOD(Dom_Element, getElementsByTagName) } /* }}} end dom_element_get_elements_by_tag_name */ +PHP_METHOD(Dom_Element, getElementsByClassName) +{ + dom_object *intern, *namednode; + zend_string *class_names; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "P", &class_names) == FAILURE) { + RETURN_THROWS(); + } + + if (ZSTR_LEN(class_names) > INT_MAX) { + zend_argument_value_error(1, "is too long"); + RETURN_THROWS(); + } + + DOM_GET_THIS_INTERN(intern); + + object_init_ex(return_value, dom_html_collection_class_entry); + namednode = Z_DOMOBJ_P(return_value); + + HashTable *token_set; + ALLOC_HASHTABLE(token_set); + zend_hash_init(token_set, 0, NULL, NULL, false); + dom_ordered_set_parser(token_set, ZSTR_VAL(class_names), intern->document->quirks_mode == PHP_LIBXML_QUIRKS); + + if (zend_hash_num_elements(token_set) == 0) { + php_dom_create_obj_map(intern, namednode, NULL, NULL, NULL, &php_dom_obj_map_noop); + + zend_hash_destroy(token_set); + FREE_HASHTABLE(token_set); + } else { + php_dom_create_obj_map(intern, namednode, NULL, NULL, NULL, &php_dom_obj_map_by_class_name); + + dom_nnodemap_object *map = namednode->ptr; + map->array = token_set; + map->release_array = true; + } +} + /* should_free_result must be initialized to false */ static const xmlChar *dom_get_attribute_ns(dom_object *intern, xmlNodePtr elemp, const char *uri, size_t uri_len, const char *name, bool *should_free_result) { diff --git a/ext/dom/obj_map.c b/ext/dom/obj_map.c index 64e4cff7b6ffe..72ecead3d7b48 100644 --- a/ext/dom/obj_map.c +++ b/ext/dom/obj_map.c @@ -24,6 +24,7 @@ #if defined(HAVE_LIBXML) && defined(HAVE_DOM) #include "php_dom.h" #include "obj_map.h" +#include "token_list.h" static zend_always_inline void objmap_cache_release_cached_obj(dom_nnodemap_object *objmap) { @@ -40,6 +41,30 @@ static zend_always_inline void reset_objmap_cache(dom_nnodemap_object *objmap) objmap->cached_length = -1; } +static bool dom_matches_class_name(const dom_nnodemap_object *map, const xmlNode *nodep) +{ + bool ret = false; + + if (nodep->type == XML_ELEMENT_NODE) { + xmlAttrPtr classes = xmlHasNsProp(nodep, BAD_CAST "class", NULL); + if (classes != NULL) { + bool should_free; + xmlChar *value = php_libxml_attr_value(classes, &should_free); + + bool quirks = map->baseobj->document->quirks_mode == PHP_LIBXML_QUIRKS; + if (dom_ordered_set_all_contained(map->array, (const char *) value, quirks)) { + ret = true; + } + + if (should_free) { + xmlFree(value); + } + } + } + + return ret; +} + /************************** * === Length methods === * **************************/ @@ -106,6 +131,24 @@ static zend_long dom_map_get_by_tag_name_length(dom_nnodemap_object *map) return count; } +static zend_long dom_map_get_by_class_name_length(dom_nnodemap_object *map) +{ + xmlNodePtr nodep = dom_object_get_node(map->baseobj); + zend_long count = 0; + if (nodep) { + xmlNodePtr basep = nodep; + nodep = php_dom_first_child_of_container_node(basep); + + while (nodep != NULL) { + if (dom_matches_class_name(map, nodep)) { + count++; + } + nodep = php_dom_next_in_tree_order(nodep, basep); + } + } + return count; +} + static zend_long dom_map_get_zero_length(dom_nnodemap_object *map) { return 0; @@ -276,6 +319,10 @@ static void dom_map_collection_named_item_elements_iter(dom_nnodemap_object *map } } +static void dom_map_collection_named_item_null(dom_nnodemap_object *map, php_dom_obj_map_collection_iter *iter) +{ +} + static void dom_map_get_by_tag_name_item(dom_nnodemap_object *map, zend_long index, zval *return_value) { xmlNodePtr nodep = dom_object_get_node(map->baseobj); @@ -292,12 +339,50 @@ static void dom_map_get_by_tag_name_item(dom_nnodemap_object *map, zend_long ind } } +static void dom_map_get_by_class_name_item(dom_nnodemap_object *map, zend_long index, zval *return_value) +{ + xmlNodePtr nodep = dom_object_get_node(map->baseobj); + xmlNodePtr itemnode = NULL; + if (nodep && index >= 0) { + xmlNodePtr basep = nodep; + dom_node_idx_pair start_point = dom_obj_map_get_start_point(map, nodep, index); + if (start_point.node) { + itemnode = php_dom_next_in_tree_order(start_point.node, basep); + } else { + itemnode = php_dom_first_child_of_container_node(nodep); + } + + do { + --start_point.index; + while (itemnode != NULL && !dom_matches_class_name(map, itemnode)) { + itemnode = php_dom_next_in_tree_order(itemnode, basep); + } + } while (start_point.index > 0 && itemnode); + } + dom_ret_node_to_zobj(map, itemnode, return_value); + if (itemnode) { + dom_map_cache_obj(map, itemnode, index, return_value); + } +} + static void dom_map_collection_named_item_by_tag_name_iter(dom_nnodemap_object *map, php_dom_obj_map_collection_iter *iter) { iter->candidate = dom_get_elements_by_tag_name_ns_raw(iter->basep, iter->candidate, map->ns, map->local, map->local_lower, &iter->cur, iter->next); iter->next = iter->cur + 1; } +static void dom_map_collection_named_item_by_class_name_iter(dom_nnodemap_object *map, php_dom_obj_map_collection_iter *iter) +{ + xmlNodePtr basep = iter->basep; + xmlNodePtr nodep = iter->candidate ? php_dom_next_in_tree_order(iter->candidate, basep) : php_dom_first_child_of_container_node(basep); + + while (nodep != NULL && !dom_matches_class_name(map, nodep)) { + nodep = php_dom_next_in_tree_order(nodep, basep); + } + + iter->candidate = nodep; +} + static void dom_map_get_null_item(dom_nnodemap_object *map, zend_long index, zval *return_value) { RETURN_NULL(); @@ -478,6 +563,16 @@ const php_dom_obj_map_handler php_dom_obj_map_by_tag_name = { .nameless = true, }; +const php_dom_obj_map_handler php_dom_obj_map_by_class_name = { + .length = dom_map_get_by_class_name_length, + .get_item = dom_map_get_by_class_name_item, + .get_ns_named_item = dom_map_get_ns_named_item_null, + .has_ns_named_item = dom_map_has_ns_named_item_null, + .collection_named_item_iter = dom_map_collection_named_item_by_class_name_iter, + .use_cache = true, + .nameless = true, +}; + const php_dom_obj_map_handler php_dom_obj_map_child_nodes = { .length = dom_map_get_nodes_length, .get_item = dom_map_get_nodes_item, @@ -533,7 +628,7 @@ const php_dom_obj_map_handler php_dom_obj_map_noop = { .get_item = dom_map_get_null_item, .get_ns_named_item = dom_map_get_ns_named_item_null, .has_ns_named_item = dom_map_has_ns_named_item_null, - .collection_named_item_iter = NULL, + .collection_named_item_iter = dom_map_collection_named_item_null, .use_cache = false, .nameless = true, }; diff --git a/ext/dom/obj_map.h b/ext/dom/obj_map.h index 97ec43f0011f8..e7231eb7b10a8 100644 --- a/ext/dom/obj_map.h +++ b/ext/dom/obj_map.h @@ -63,6 +63,7 @@ zend_long php_dom_get_nodelist_length(dom_object *obj); extern const php_dom_obj_map_handler php_dom_obj_map_attributes; extern const php_dom_obj_map_handler php_dom_obj_map_by_tag_name; +extern const php_dom_obj_map_handler php_dom_obj_map_by_class_name; extern const php_dom_obj_map_handler php_dom_obj_map_child_elements; extern const php_dom_obj_map_handler php_dom_obj_map_child_nodes; extern const php_dom_obj_map_handler php_dom_obj_map_nodeset; diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index fd0927fc0ea12..7236e80b4d247 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -1659,6 +1659,7 @@ public function removeAttributeNode(Attr $attr) : Attr {} public function getElementsByTagName(string $qualifiedName): HTMLCollection {} public function getElementsByTagNameNS(?string $namespace, string $localName): HTMLCollection {} + public function getElementsByClassName(string $classNames): HTMLCollection {} public function insertAdjacentElement(AdjacentPosition $where, Element $element): ?Element {} public function insertAdjacentText(AdjacentPosition $where, string $data): void {} @@ -1986,6 +1987,8 @@ abstract class Document extends Node implements ParentNode public function getElementsByTagName(string $qualifiedName): HTMLCollection {} /** @implementation-alias Dom\Element::getElementsByTagNameNS */ public function getElementsByTagNameNS(?string $namespace, string $localName): HTMLCollection {} + /** @implementation-alias Dom\Element::getElementsByClassName */ + public function getElementsByClassName(string $classNames): HTMLCollection {} public function createElement(string $localName): Element {} public function createElementNS(?string $namespace, string $qualifiedName): Element {} diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index be7a5c0713ad4..73efc8696e92f 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: 2119512797f6d51d9835660cd0eccd3ba83417a9 */ + * Stub hash: 757889c0ca89cc8e9905ba465e0621fe89b6e716 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_dom_import_simplexml, 0, 1, DOMAttr|DOMElement, 0) ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0) @@ -775,6 +775,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Dom_Element_getElementsByTa ZEND_ARG_TYPE_INFO(0, localName, IS_STRING, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Dom_Element_getElementsByClassName, 0, 1, Dom\\HTMLCollection, 0) + ZEND_ARG_TYPE_INFO(0, classNames, IS_STRING, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Dom_Element_insertAdjacentElement, 0, 2, Dom\\Element, 1) ZEND_ARG_OBJ_INFO(0, where, Dom\\AdjacentPosition, 0) ZEND_ARG_OBJ_INFO(0, element, Dom\\Element, 0) @@ -906,6 +910,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_Dom_Document_getElementsByTagNameNS arginfo_class_Dom_Element_getElementsByTagNameNS +#define arginfo_class_Dom_Document_getElementsByClassName arginfo_class_Dom_Element_getElementsByClassName + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Dom_Document_createElement, 0, 1, Dom\\Element, 0) ZEND_ARG_TYPE_INFO(0, localName, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -1278,6 +1284,7 @@ ZEND_METHOD(Dom_Element, setAttributeNodeNS); ZEND_METHOD(Dom_Element, removeAttributeNode); ZEND_METHOD(Dom_Element, getElementsByTagName); ZEND_METHOD(Dom_Element, getElementsByTagNameNS); +ZEND_METHOD(Dom_Element, getElementsByClassName); ZEND_METHOD(Dom_Element, insertAdjacentElement); ZEND_METHOD(Dom_Element, insertAdjacentText); ZEND_METHOD(Dom_Element, insertAdjacentHTML); @@ -1653,6 +1660,7 @@ static const zend_function_entry class_Dom_Element_methods[] = { ZEND_ME(Dom_Element, removeAttributeNode, arginfo_class_Dom_Element_removeAttributeNode, ZEND_ACC_PUBLIC) ZEND_ME(Dom_Element, getElementsByTagName, arginfo_class_Dom_Element_getElementsByTagName, ZEND_ACC_PUBLIC) ZEND_ME(Dom_Element, getElementsByTagNameNS, arginfo_class_Dom_Element_getElementsByTagNameNS, ZEND_ACC_PUBLIC) + ZEND_ME(Dom_Element, getElementsByClassName, arginfo_class_Dom_Element_getElementsByClassName, ZEND_ACC_PUBLIC) ZEND_ME(Dom_Element, insertAdjacentElement, arginfo_class_Dom_Element_insertAdjacentElement, ZEND_ACC_PUBLIC) ZEND_ME(Dom_Element, insertAdjacentText, arginfo_class_Dom_Element_insertAdjacentText, ZEND_ACC_PUBLIC) ZEND_ME(Dom_Element, insertAdjacentHTML, arginfo_class_Dom_Element_insertAdjacentHTML, ZEND_ACC_PUBLIC) @@ -1721,6 +1729,7 @@ static const zend_function_entry class_Dom_DocumentFragment_methods[] = { static const zend_function_entry class_Dom_Document_methods[] = { ZEND_RAW_FENTRY("getElementsByTagName", zim_Dom_Element_getElementsByTagName, arginfo_class_Dom_Document_getElementsByTagName, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getElementsByTagNameNS", zim_Dom_Element_getElementsByTagNameNS, arginfo_class_Dom_Document_getElementsByTagNameNS, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("getElementsByClassName", zim_Dom_Element_getElementsByClassName, arginfo_class_Dom_Document_getElementsByClassName, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_ME(Dom_Document, createElement, arginfo_class_Dom_Document_createElement, ZEND_ACC_PUBLIC) ZEND_ME(Dom_Document, createElementNS, arginfo_class_Dom_Document_createElementNS, ZEND_ACC_PUBLIC) ZEND_RAW_FENTRY("createDocumentFragment", zim_DOMDocument_createDocumentFragment, arginfo_class_Dom_Document_createDocumentFragment, ZEND_ACC_PUBLIC, NULL, NULL) diff --git a/ext/dom/tests/modern/common/Element_getElementsByClassName.phpt b/ext/dom/tests/modern/common/Element_getElementsByClassName.phpt new file mode 100644 index 0000000000000..0d40ac785afda --- /dev/null +++ b/ext/dom/tests/modern/common/Element_getElementsByClassName.phpt @@ -0,0 +1,42 @@ +--TEST-- +Dom\Element::getElementsByClassName() +--EXTENSIONS-- +dom +--FILE-- + +
+2
+3
+ + + + +HTML, LIBXML_NOERROR); +$collection = $dom->documentElement->getElementsByClassName("bar"); + +echo "There are {$dom->getElementsByClassName("foo \n bar")->count()} items in the document in total that have both \"foo\" and \"bar\"\n"; + +echo "There are {$collection->count()} \"bar\" items\n"; + +foreach ($collection as $key => $node) { + var_dump($key, $node->tagName); + var_dump($node === $collection->item($key)); +} + +var_dump($collection->namedItem("here")->textContent); + +?> +--EXPECT-- +There are 1 items in the document in total that have both "foo" and "bar" +There are 4 "bar" items +int(0) +string(3) "DIV" +bool(false) +int(1) +string(1) "P" +bool(false) +string(1) "1" diff --git a/ext/dom/tests/modern/common/Element_getElementsByClassName_empty.phpt b/ext/dom/tests/modern/common/Element_getElementsByClassName_empty.phpt new file mode 100644 index 0000000000000..719819c742d09 --- /dev/null +++ b/ext/dom/tests/modern/common/Element_getElementsByClassName_empty.phpt @@ -0,0 +1,32 @@ +--TEST-- +Dom\Element::getElementsByClassName() empty class names +--EXTENSIONS-- +dom +--FILE-- + + + +HTML, LIBXML_NOERROR); + +$collection = $dom->documentElement->getElementsByClassName(""); +var_dump($collection->count()); + +foreach ($collection as $node) { + throw new Error("unreachable"); +} + +var_dump($dom->getElementsByClassName(" ")->count()); +var_dump($dom->getElementsByClassName("\t")->count()); +var_dump($dom->getElementsByClassName("\t\n\f\v")->count()); +var_dump($dom->getElementsByClassName("\t\n\f\v")->namedItem("cjild")); + +?> +--EXPECT-- +int(0) +int(0) +int(0) +int(0) +NULL diff --git a/ext/dom/token_list.c b/ext/dom/token_list.c index fe768e17b2e9c..cdfb408d4548d 100644 --- a/ext/dom/token_list.c +++ b/ext/dom/token_list.c @@ -51,7 +51,7 @@ static zend_always_inline void dom_add_token(HashTable *ht, zend_string *token) /* https://dom.spec.whatwg.org/#concept-ordered-set-parser * and https://infra.spec.whatwg.org/#split-on-ascii-whitespace */ -static void dom_ordered_set_parser(HashTable *token_set, const char *position) +void dom_ordered_set_parser(HashTable *token_set, const char *position, bool to_lowercase) { /* Adapted steps from "split on ASCII whitespace" such that that loop directly appends to the token set. */ @@ -72,6 +72,9 @@ static void dom_ordered_set_parser(HashTable *token_set, const char *position) /* 4.2. Append token to tokens. */ zend_string *token = zend_string_init(start, length, false); + if (to_lowercase) { + zend_str_tolower(ZSTR_VAL(token), length); + } dom_add_token(token_set, token); zend_string_release_ex(token, false); @@ -83,6 +86,53 @@ static void dom_ordered_set_parser(HashTable *token_set, const char *position) * => That's the token set. */ } +/* This returns true if all tokens in "token_set" are found in "value". */ +bool dom_ordered_set_all_contained(HashTable *token_set, const char *value, bool to_lowercase) +{ + /* This code is conceptually close to dom_ordered_set_parser(), + * but without building a hash table. + * Since the storage of the token set maps a value on itself, + * we can reuse that storage as a "seen" flag by setting it to NULL. */ + zval *zv; + + uint32_t still_needed = zend_hash_num_elements(token_set); + + value += strspn(value, ascii_whitespace); + + while (*value != '\0' && still_needed > 0) { + const char *start = value; + value += strcspn(value, ascii_whitespace); + size_t length = value - start; + + if (to_lowercase) { + ALLOCA_FLAG(use_heap) + char *lc_str = zend_str_tolower_copy(do_alloca(length + 1, use_heap), start, length); + zv = zend_hash_str_find(token_set, lc_str, length); + free_alloca(lc_str, use_heap); + } else { + zv = zend_hash_str_find(token_set, start, length); + } + if (zv) { + if (Z_STR_P(zv)) { + still_needed--; + Z_STR_P(zv) = NULL; + } + } + + value += strspn(value, ascii_whitespace); + } + + /* Restore "seen" flag. */ + zend_string *k; + ZEND_HASH_FOREACH_STR_KEY_VAL(token_set, k, zv) { + if (!Z_STR_P(zv)) { + Z_STR_P(zv) = k; + } + } ZEND_HASH_FOREACH_END(); + + return still_needed == 0; +} + /* https://dom.spec.whatwg.org/#concept-ordered-set-serializer */ static char *dom_ordered_set_serializer(HashTable *token_set) { @@ -166,7 +216,7 @@ static void dom_token_list_update_set(dom_token_list_object *intern, HashTable * xmlChar *value = dom_token_list_get_class_value(attr, &should_free); if (value != NULL) { /* 2. Otherwise, parse the token set. */ - dom_ordered_set_parser(token_set, (const char *) value); + dom_ordered_set_parser(token_set, (const char *) value, false); intern->cached_string = estrdup((const char *) value); } else { intern->cached_string = NULL; diff --git a/ext/dom/token_list.h b/ext/dom/token_list.h index 4711852c4d876..7c8a6f612ffa4 100644 --- a/ext/dom/token_list.h +++ b/ext/dom/token_list.h @@ -35,6 +35,8 @@ static inline dom_token_list_object *php_dom_token_list_from_dom_obj(dom_object return (dom_token_list_object *)((char *) obj - XtOffsetOf(dom_token_list_object, dom)); } +void dom_ordered_set_parser(HashTable *token_set, const char *position, bool to_lowercase); +bool dom_ordered_set_all_contained(HashTable *token_set, const char *value, bool to_lowercase); void dom_token_list_ctor(dom_token_list_object *intern, dom_object *element_obj); void dom_token_list_free_obj(zend_object *object); zval *dom_token_list_read_dimension(zend_object *object, zval *offset, int type, zval *rv); From 00e23031cce0563a795dd458f4030f92aaabad17 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 13 Jul 2025 21:19:39 +0200 Subject: [PATCH 2/6] fixes --- ext/dom/obj_map.c | 10 +++-- .../Element_getElementsByClassName.phpt | 42 ------------------- .../Element_getElementsByClassName_empty.phpt | 2 +- 3 files changed, 8 insertions(+), 46 deletions(-) delete mode 100644 ext/dom/tests/modern/common/Element_getElementsByClassName.phpt diff --git a/ext/dom/obj_map.c b/ext/dom/obj_map.c index 72ecead3d7b48..9c017c4d11a6b 100644 --- a/ext/dom/obj_map.c +++ b/ext/dom/obj_map.c @@ -344,10 +344,14 @@ static void dom_map_get_by_class_name_item(dom_nnodemap_object *map, zend_long i xmlNodePtr nodep = dom_object_get_node(map->baseobj); xmlNodePtr itemnode = NULL; if (nodep && index >= 0) { - xmlNodePtr basep = nodep; dom_node_idx_pair start_point = dom_obj_map_get_start_point(map, nodep, index); if (start_point.node) { - itemnode = php_dom_next_in_tree_order(start_point.node, basep); + if (start_point.index > 0) { + /* Only start iteration at next point if we actually have an index to seek to. */ + itemnode = php_dom_next_in_tree_order(start_point.node, nodep); + } else { + itemnode = start_point.node; + } } else { itemnode = php_dom_first_child_of_container_node(nodep); } @@ -355,7 +359,7 @@ static void dom_map_get_by_class_name_item(dom_nnodemap_object *map, zend_long i do { --start_point.index; while (itemnode != NULL && !dom_matches_class_name(map, itemnode)) { - itemnode = php_dom_next_in_tree_order(itemnode, basep); + itemnode = php_dom_next_in_tree_order(itemnode, nodep); } } while (start_point.index > 0 && itemnode); } diff --git a/ext/dom/tests/modern/common/Element_getElementsByClassName.phpt b/ext/dom/tests/modern/common/Element_getElementsByClassName.phpt deleted file mode 100644 index 0d40ac785afda..0000000000000 --- a/ext/dom/tests/modern/common/Element_getElementsByClassName.phpt +++ /dev/null @@ -1,42 +0,0 @@ ---TEST-- -Dom\Element::getElementsByClassName() ---EXTENSIONS-- -dom ---FILE-- - - -2
-3
- - - - -HTML, LIBXML_NOERROR); -$collection = $dom->documentElement->getElementsByClassName("bar"); - -echo "There are {$dom->getElementsByClassName("foo \n bar")->count()} items in the document in total that have both \"foo\" and \"bar\"\n"; - -echo "There are {$collection->count()} \"bar\" items\n"; - -foreach ($collection as $key => $node) { - var_dump($key, $node->tagName); - var_dump($node === $collection->item($key)); -} - -var_dump($collection->namedItem("here")->textContent); - -?> ---EXPECT-- -There are 1 items in the document in total that have both "foo" and "bar" -There are 4 "bar" items -int(0) -string(3) "DIV" -bool(false) -int(1) -string(1) "P" -bool(false) -string(1) "1" diff --git a/ext/dom/tests/modern/common/Element_getElementsByClassName_empty.phpt b/ext/dom/tests/modern/common/Element_getElementsByClassName_empty.phpt index 719819c742d09..d2e3b0ea22b9b 100644 --- a/ext/dom/tests/modern/common/Element_getElementsByClassName_empty.phpt +++ b/ext/dom/tests/modern/common/Element_getElementsByClassName_empty.phpt @@ -21,7 +21,7 @@ foreach ($collection as $node) { var_dump($dom->getElementsByClassName(" ")->count()); var_dump($dom->getElementsByClassName("\t")->count()); var_dump($dom->getElementsByClassName("\t\n\f\v")->count()); -var_dump($dom->getElementsByClassName("\t\n\f\v")->namedItem("cjild")); +var_dump($dom->getElementsByClassName("\t\n\f\v")->namedItem("child")); ?> --EXPECT-- From 85af612afa33e7a976903e68eff9229c4e7902b5 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 13 Jul 2025 21:19:54 +0200 Subject: [PATCH 3/6] Tests --- ...ent_getElementsByClassName_non_quirks.phpt | 55 ++++++++++++++++ ...Element_getElementsByClassName_quirks.phpt | 62 +++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 ext/dom/tests/modern/common/Element_getElementsByClassName_non_quirks.phpt create mode 100644 ext/dom/tests/modern/common/Element_getElementsByClassName_quirks.phpt diff --git a/ext/dom/tests/modern/common/Element_getElementsByClassName_non_quirks.phpt b/ext/dom/tests/modern/common/Element_getElementsByClassName_non_quirks.phpt new file mode 100644 index 0000000000000..e658e3399ca0d --- /dev/null +++ b/ext/dom/tests/modern/common/Element_getElementsByClassName_non_quirks.phpt @@ -0,0 +1,55 @@ +--TEST-- +Dom\Element::getElementsByClassName() non quirks mode +--EXTENSIONS-- +dom +--FILE-- + +1
+ +3
+4
+ + +7
+ +9
+10
+ +2
+3
+4
+ + + +HTML, LIBXML_NOERROR); +$collection = $dom->documentElement->getElementsByClassName("Bar"); + +echo "There are {$dom->getElementsByClassName("foo \n bar")->count()} items in the document in total that have both \"foo\" and \"bar\"\n"; + +echo "There are {$collection->count()} \"Bar\" items\n"; + +foreach ($collection as $key => $node) { + echo "--- Key $key ---\n"; + var_dump($node->tagName, $node->textContent); + var_dump($node === $collection->item($key)); +} + +var_dump($collection->namedItem("here")->textContent); + +?> +--EXPECT-- +There are 1 items in the document in total that have both "foo" and "bar" +There are 4 "Bar" items +--- Key 0 --- +string(3) "DIV" +string(56) " + + 1 + 2 + 3 + 4 + + +" +bool(true) +--- Key 1 --- +string(1) "P" +string(9) " + " +bool(true) +--- Key 2 --- +string(1) "P" +string(1) "1" +bool(true) +--- Key 3 --- +string(1) "P" +string(1) "4" +bool(true) +string(1) "1" From 8e7f3201612b4652f3f730cf85318db53992fc97 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 13 Jul 2025 23:12:36 +0200 Subject: [PATCH 4/6] Can use HASH_MAP --- ext/dom/token_list.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/dom/token_list.c b/ext/dom/token_list.c index cdfb408d4548d..84a3dc10afebc 100644 --- a/ext/dom/token_list.c +++ b/ext/dom/token_list.c @@ -124,7 +124,7 @@ bool dom_ordered_set_all_contained(HashTable *token_set, const char *value, bool /* Restore "seen" flag. */ zend_string *k; - ZEND_HASH_FOREACH_STR_KEY_VAL(token_set, k, zv) { + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(token_set, k, zv) { if (!Z_STR_P(zv)) { Z_STR_P(zv) = k; } From fd749fb8c896024cdafa099c4dde5586f27471dc Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 14 Jul 2025 19:52:49 +0200 Subject: [PATCH 5/6] Make test a bit more logical --- ...Element_getElementsByClassName_quirks.phpt | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/ext/dom/tests/modern/common/Element_getElementsByClassName_quirks.phpt b/ext/dom/tests/modern/common/Element_getElementsByClassName_quirks.phpt index a02679f2f8626..52e964ea43177 100644 --- a/ext/dom/tests/modern/common/Element_getElementsByClassName_quirks.phpt +++ b/ext/dom/tests/modern/common/Element_getElementsByClassName_quirks.phpt @@ -7,12 +7,12 @@ dom $dom = Dom\HTMLDocument::createFromString(<< - -2
+1
+3
4
- +