php-src/ext/dom/html_collection.c
Niels Dossche c7c6a79bd0
Add support for ParentNode::$children (#18908)
ParentNode::$children returns a HTMLCollection of all directly
descendant child elements of a container.

I had to move around some properties such that the ParentNode property
offsets are always at a fixed offset, to simplify the code.
This also adds the necessary code to deal with GC cycles in
HTMLCollections.
Furthermore, we also disable cloning a HTMLCollection as that never
worked and furthermore it also conflicts with the [[SameObject]] WebIDL
requirement of $children.
2025-06-27 09:03:50 +02:00

176 lines
5.7 KiB
C

/*
+----------------------------------------------------------------------+
| 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 <nielsdos@php.net> |
+----------------------------------------------------------------------+
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "php.h"
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
#include "php_dom.h"
#include "obj_map.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) {
zend_long cur = 0;
zend_long next = cur; /* not +1, otherwise we skip the first candidate */
xmlNodePtr candidate = basep->children;
bool iterate_tag_name = objmap->handler == &php_dom_obj_map_by_tag_name;
while (candidate != NULL) {
if (iterate_tag_name) {
candidate = dom_get_elements_by_tag_name_ns_raw(basep, candidate, objmap->ns, objmap->local, objmap->local_lower, &cur, next);
if (candidate == NULL) {
break;
}
next = cur + 1;
} else {
if (candidate->type != XML_ELEMENT_NODE) {
candidate = candidate->next;
continue;
}
}
ZEND_ASSERT(candidate->type == XML_ELEMENT_NODE);
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;
}
}
if (!iterate_tag_name) {
candidate = candidate->next;
}
}
}
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_obj_map_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));
}
}
HashTable *dom_html_collection_get_gc(zend_object *object, zval **table, int *n)
{
dom_nnodemap_object *objmap = php_dom_obj_from_obj(object)->ptr;
if (objmap->baseobj) {
zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
zend_get_gc_buffer_add_obj(gc_buffer, &objmap->baseobj->std);
zend_get_gc_buffer_use(gc_buffer, table, n);
if (object->properties == NULL && object->ce->default_properties_count == 0) {
return NULL;
} else {
return zend_std_get_properties(object);
}
} else {
return zend_std_get_gc(object, table, n);
}
}
#endif