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.
This commit is contained in:
Niels Dossche 2025-06-27 09:03:50 +02:00 committed by GitHub
parent 1b7f4567cb
commit c7c6a79bd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 372 additions and 129 deletions

1
NEWS
View file

@ -74,6 +74,7 @@ PHP NEWS
- DOM:
. Added Dom\Element::$outerHTML. (nielsdos)
. Added Dom\Element::insertAdjacentHTML(). (nielsdos)
. Added $children property to ParentNode implementations. (nielsdos)
- Enchant:
. Added enchant_dict_remove_from_session(). (nielsdos)

View file

@ -185,6 +185,7 @@ PHP 8.5 UPGRADE NOTES
- DOM:
. Added Dom\Element::$outerHTML.
. Added $children property to Dom\ParentNode implementations.
- EXIF:
. Add OffsetTime* Exif tags.

View file

@ -109,6 +109,7 @@ zend_result dom_entity_reference_child_nodes_read(dom_object *obj, zval *retval)
zend_result dom_namednodemap_length_read(dom_object *obj, zval *retval);
/* parent node properties */
zend_result dom_parent_node_children_read(dom_object *obj, zval *retval);
zend_result dom_parent_node_first_element_child_read(dom_object *obj, zval *retval);
zend_result dom_parent_node_last_element_child_read(dom_object *obj, zval *retval);
zend_result dom_parent_node_child_element_count(dom_object *obj, zval *retval);

View file

@ -177,18 +177,21 @@ zend_result dom_element_class_name_write(dom_object *obj, zval *newval)
}
/* }}} */
zval *dom_element_class_list_zval(dom_object *obj)
zval *dom_get_prop_checked_offset(dom_object *obj, uint32_t offset, const char *name)
{
const uint32_t PROP_INDEX = 0;
#if ZEND_DEBUG
zend_string *class_list_str = ZSTR_INIT_LITERAL("classList", false);
const zend_property_info *prop_info = zend_get_property_info(dom_modern_element_class_entry, class_list_str, 0);
zend_string_release_ex(class_list_str, false);
ZEND_ASSERT(OBJ_PROP_TO_NUM(prop_info->offset) == PROP_INDEX);
zend_string *name_zstr = ZSTR_INIT_LITERAL(name, false);
const zend_property_info *prop_info = zend_get_property_info(obj->std.ce, name_zstr, 0);
zend_string_release_ex(name_zstr, false);
ZEND_ASSERT(OBJ_PROP_TO_NUM(prop_info->offset) == offset);
#endif
return OBJ_PROP_NUM(&obj->std, PROP_INDEX);
return OBJ_PROP_NUM(&obj->std, offset);
}
zval *dom_element_class_list_zval(dom_object *obj)
{
return dom_get_prop_checked_offset(obj, 1, "classList");
}
/* {{{ classList TokenList

View file

@ -50,12 +50,23 @@ static dom_named_item dom_html_collection_named_item(zend_string *key, zend_obje
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) {
candidate = dom_get_elements_by_tag_name_ns_raw(basep, candidate, objmap->ns, objmap->local, objmap->local_lower, &cur, next);
if (candidate == NULL) {
break;
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; */
@ -73,7 +84,9 @@ static dom_named_item dom_html_collection_named_item(zend_string *key, zend_obje
}
}
next = cur + 1;
if (!iterate_tag_name) {
candidate = candidate->next;
}
}
}
@ -141,4 +154,23 @@ int dom_html_collection_has_dimension(zend_object *object, zval *member, int che
}
}
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

View file

@ -19,5 +19,6 @@
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);
HashTable *dom_html_collection_get_gc(zend_object *object, zval **table, int *n);
#endif

View file

@ -84,16 +84,7 @@ typedef struct dom_decoding_encoding_ctx {
/* https://dom.spec.whatwg.org/#dom-document-implementation */
zend_result dom_modern_document_implementation_read(dom_object *obj, zval *retval)
{
const uint32_t PROP_INDEX = 0;
#if ZEND_DEBUG
zend_string *implementation_str = ZSTR_INIT_LITERAL("implementation", false);
const zend_property_info *prop_info = zend_get_property_info(dom_abstract_base_document_class_entry, implementation_str, 0);
zend_string_release_ex(implementation_str, false);
ZEND_ASSERT(OBJ_PROP_TO_NUM(prop_info->offset) == PROP_INDEX);
#endif
zval *cached_implementation = OBJ_PROP_NUM(&obj->std, PROP_INDEX);
zval *cached_implementation = dom_get_prop_checked_offset(obj, 1, "implementation");
if (Z_ISUNDEF_P(cached_implementation)) {
php_dom_create_implementation(cached_implementation, true);
}

View file

@ -28,10 +28,7 @@
static zend_always_inline void objmap_cache_release_cached_obj(dom_nnodemap_object *objmap)
{
if (objmap->cached_obj) {
/* Since the DOM is a tree there can be no cycles. */
if (GC_DELREF(&objmap->cached_obj->std) == 0) {
zend_objects_store_del(&objmap->cached_obj->std);
}
OBJ_RELEASE(&objmap->cached_obj->std);
objmap->cached_obj = NULL;
objmap->cached_obj_index = 0;
}
@ -82,6 +79,20 @@ static zend_long dom_map_get_nodes_length(dom_nnodemap_object *map)
return count;
}
static zend_long dom_map_get_elements_length(dom_nnodemap_object *map)
{
zend_long count = 0;
xmlNodePtr nodep = dom_object_get_node(map->baseobj);
if (nodep) {
for (xmlNodePtr curnode = dom_nodelist_iter_start_first_child(nodep); curnode; curnode = curnode->next) {
if (curnode->type == XML_ELEMENT_NODE) {
count++;
}
}
}
return count;
}
static zend_long dom_map_get_by_tag_name_length(dom_nnodemap_object *map)
{
xmlNodePtr nodep = dom_object_get_node(map->baseobj);
@ -223,6 +234,38 @@ static void dom_map_get_nodes_item(dom_nnodemap_object *map, zend_long index, zv
}
}
static void dom_map_get_elements_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) {
dom_node_idx_pair start_point = dom_obj_map_get_start_point(map, nodep, index);
if (start_point.node) {
/* Guaranteed to be an element */
itemnode = start_point.node;
} else {
/* Fetch first element child */
itemnode = nodep->children;
while (itemnode && itemnode->type != XML_ELEMENT_NODE) {
itemnode = itemnode->next;
}
}
for (; start_point.index > 0 && itemnode; --start_point.index) {
do {
itemnode = itemnode->next;
} while (itemnode && itemnode->type != XML_ELEMENT_NODE);
}
if (itemnode && itemnode->type != XML_ELEMENT_NODE) {
itemnode = NULL;
}
}
dom_ret_node_to_zobj(map, itemnode, return_value);
if (itemnode) {
dom_map_cache_obj(map, itemnode, index, return_value);
}
}
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);
@ -456,6 +499,15 @@ const php_dom_obj_map_handler php_dom_obj_map_notations = {
.nameless = false,
};
const php_dom_obj_map_handler php_dom_obj_map_child_elements = {
.length = dom_map_get_elements_length,
.get_item = dom_map_get_elements_item,
.get_named_item = dom_map_get_named_item_null,
.has_named_item = dom_map_has_named_item_null,
.use_cache = true,
.nameless = true,
};
const php_dom_obj_map_handler php_dom_obj_map_noop = {
.length = dom_map_get_zero_length,
.get_item = dom_map_get_null_item,

View file

@ -57,6 +57,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_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;
extern const php_dom_obj_map_handler php_dom_obj_map_entities;

View file

@ -22,9 +22,34 @@
#include "php.h"
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
#include "../php_dom.h"
#include "../obj_map.h"
#include "../internal_helpers.h"
#include "../dom_properties.h"
zval *dom_parent_node_children(dom_object *obj)
{
return dom_get_prop_checked_offset(obj, 0, "children");
}
zend_result dom_parent_node_children_read(dom_object *obj, zval *retval)
{
zval *cached_children = dom_parent_node_children(obj);
if (Z_ISUNDEF_P(cached_children)) {
object_init_ex(cached_children, dom_html_collection_class_entry);
php_dom_create_obj_map(obj, Z_DOMOBJ_P(cached_children), NULL, NULL, NULL, &php_dom_obj_map_child_elements);
/* Handle cycles for potential TMPVARs (could also be CV but we can't differentiate).
* RC == 2 because of 1 TMPVAR and 1 in HTMLCollection. */
if (GC_REFCOUNT(&obj->std) == 2) {
gc_possible_root(Z_COUNTED_P(cached_children));
}
}
ZVAL_OBJ_COPY(retval, Z_OBJ_P(cached_children));
return SUCCESS;
}
/* {{{ firstElementChild DomParentNode
readonly=yes
URL: https://www.w3.org/TR/dom/#dom-parentnode-firstelementchild

View file

@ -96,6 +96,7 @@ PHP_DOM_EXPORT zend_class_entry *dom_namespace_info_class_entry;
static zend_object_handlers dom_object_handlers;
static zend_object_handlers dom_nnodemap_object_handlers;
static zend_object_handlers dom_nodelist_object_handlers;
static zend_object_handlers dom_unset_children_property_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;
@ -668,14 +669,35 @@ static zend_object *dom_objects_store_clone_obj(zend_object *zobject) /* {{{ */
static zend_object *dom_modern_element_clone_obj(zend_object *zobject)
{
zend_object *clone = dom_objects_store_clone_obj(zobject);
dom_object *intern = php_dom_obj_from_obj(clone);
/* The $classList property is unique per element, and cached due to its [[SameObject]] requirement.
* Remove it from the clone so the clone will get a fresh instance upon demand. */
zval *class_list = dom_element_class_list_zval(php_dom_obj_from_obj(clone));
zval *class_list = dom_element_class_list_zval(intern);
if (!Z_ISUNDEF_P(class_list)) {
zval_ptr_dtor(class_list);
ZVAL_UNDEF(class_list);
}
/* Likewise for $children */
zval *children = dom_parent_node_children(intern);
if (!Z_ISUNDEF_P(children)) {
zval_ptr_dtor(children);
ZVAL_UNDEF(children);
}
return clone;
}
static zend_object *dom_clone_obj_unset_children_property(zend_object *zobject)
{
zend_object *clone = dom_objects_store_clone_obj(zobject);
dom_object *intern = php_dom_obj_from_obj(clone);
zval *children = dom_parent_node_children(intern);
if (!Z_ISUNDEF_P(children)) {
zval_ptr_dtor(children);
ZVAL_UNDEF(children);
}
return clone;
}
@ -777,6 +799,9 @@ PHP_MINIT_FUNCTION(dom)
memcpy(&dom_modern_element_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
dom_modern_element_object_handlers.clone_obj = dom_modern_element_clone_obj;
memcpy(&dom_unset_children_property_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
dom_unset_children_property_object_handlers.clone_obj = dom_clone_obj_unset_children_property;
memcpy(&dom_nnodemap_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
dom_nnodemap_object_handlers.free_obj = dom_nnodemap_objects_free_storage;
dom_nnodemap_object_handlers.read_dimension = dom_nodemap_read_dimension;
@ -797,6 +822,8 @@ PHP_MINIT_FUNCTION(dom)
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;
dom_html_collection_object_handlers.get_gc = dom_html_collection_get_gc;
dom_html_collection_object_handlers.clone_obj = NULL;
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);
@ -911,9 +938,10 @@ PHP_MINIT_FUNCTION(dom)
dom_modern_documentfragment_class_entry = register_class_Dom_DocumentFragment(dom_modern_node_class_entry, dom_modern_parentnode_class_entry);
dom_modern_documentfragment_class_entry->create_object = dom_objects_new;
dom_modern_documentfragment_class_entry->default_object_handlers = &dom_object_handlers;
dom_modern_documentfragment_class_entry->default_object_handlers = &dom_unset_children_property_object_handlers;
zend_hash_init(&dom_modern_documentfragment_prop_handlers, 0, NULL, NULL, true);
DOM_REGISTER_PROP_HANDLER(&dom_modern_documentfragment_prop_handlers, "children", dom_parent_node_children_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_modern_documentfragment_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_modern_documentfragment_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_modern_documentfragment_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
@ -922,7 +950,7 @@ PHP_MINIT_FUNCTION(dom)
zend_hash_add_new_ptr(&classes, dom_modern_documentfragment_class_entry->name, &dom_modern_documentfragment_prop_handlers);
dom_abstract_base_document_class_entry = register_class_Dom_Document(dom_modern_node_class_entry, dom_modern_parentnode_class_entry);
dom_abstract_base_document_class_entry->default_object_handlers = &dom_object_handlers;
dom_abstract_base_document_class_entry->default_object_handlers = &dom_unset_children_property_object_handlers;
zend_hash_init(&dom_abstract_base_document_prop_handlers, 0, NULL, NULL, true);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "implementation", dom_modern_document_implementation_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "URL", dom_document_document_uri_read, dom_document_document_uri_write);
@ -932,6 +960,7 @@ PHP_MINIT_FUNCTION(dom)
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "inputEncoding", dom_document_encoding_read, dom_html_document_encoding_write);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "doctype", dom_document_doctype_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "documentElement", dom_document_document_element_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "children", dom_parent_node_children_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
@ -1118,6 +1147,7 @@ PHP_MINIT_FUNCTION(dom)
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "className", dom_element_class_name_read, dom_element_class_name_write);
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "classList", dom_element_class_list_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "attributes", dom_node_attributes_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "children", dom_parent_node_children_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
@ -1534,8 +1564,8 @@ void dom_nnodemap_objects_free_storage(zend_object *object) /* {{{ */
dom_nnodemap_object *objmap = (dom_nnodemap_object *)intern->ptr;
if (objmap) {
if (objmap->cached_obj && GC_DELREF(&objmap->cached_obj->std) == 0) {
zend_objects_store_del(&objmap->cached_obj->std);
if (objmap->cached_obj) {
OBJ_RELEASE(&objmap->cached_obj->std);
}
if (objmap->release_local) {
dom_zend_string_release_from_char_pointer(objmap->local);

View file

@ -160,7 +160,11 @@ bool php_dom_create_nullable_object(xmlNodePtr obj, zval *return_value, dom_obje
xmlNodePtr dom_clone_node(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr node, xmlDocPtr doc, bool recursive);
void dom_set_document_ref_pointers(xmlNodePtr node, php_libxml_ref_obj *document);
void dom_set_document_ref_pointers_attr(xmlAttrPtr attr, php_libxml_ref_obj *document);
/* Prop getters by offset */
zval *dom_get_prop_checked_offset(dom_object *obj, uint32_t offset, const char *name);
zval *dom_element_class_list_zval(dom_object *obj);
zval *dom_parent_node_children(dom_object *obj);
typedef enum {
DOM_LOAD_STRING = 0,

View file

@ -1584,6 +1584,36 @@ namespace Dom
*/
public string $tagName;
/**
* @readonly
*/
public HTMLCollection $children;
/**
* @readonly
* @virtual
*/
public ?Element $firstElementChild;
/**
* @readonly
* @virtual
*/
public ?Element $lastElementChild;
/**
* @readonly
* @virtual
*/
public int $childElementCount;
/**
* @readonly
* @virtual
*/
public ?Element $previousElementSibling;
/**
* @readonly
* @virtual
*/
public ?Element $nextElementSibling;
/** @virtual */
public string $id;
/** @virtual */
@ -1634,32 +1664,6 @@ namespace Dom
public function insertAdjacentText(AdjacentPosition $where, string $data): void {}
public function insertAdjacentHTML(AdjacentPosition $where, string $string): void {}
/**
* @readonly
* @virtual
*/
public ?Element $firstElementChild;
/**
* @readonly
* @virtual
*/
public ?Element $lastElementChild;
/**
* @readonly
* @virtual
*/
public int $childElementCount;
/**
* @readonly
* @virtual
*/
public ?Element $previousElementSibling;
/**
* @readonly
* @virtual
*/
public ?Element $nextElementSibling;
/** @implementation-alias DOMElement::setIdAttribute */
public function setIdAttribute(string $qualifiedName, bool $isId): void {}
/** @implementation-alias DOMElement::setIdAttributeNS */
@ -1863,6 +1867,10 @@ namespace Dom
class DocumentFragment extends Node implements ParentNode
{
/**
* @readonly
*/
public HTMLCollection $children;
/**
* @readonly
* @virtual
@ -1931,6 +1939,26 @@ namespace Dom
abstract class Document extends Node implements ParentNode
{
/**
* @readonly
*/
public HTMLCollection $children;
/**
* @readonly
* @virtual
*/
public ?Element $firstElementChild;
/**
* @readonly
* @virtual
*/
public ?Element $lastElementChild;
/**
* @readonly
* @virtual
*/
public int $childElementCount;
/** @readonly */
public Implementation $implementation;
/** @virtual */
@ -1979,22 +2007,6 @@ namespace Dom
/** @implementation-alias DOMDocument::createAttributeNS */
public function createAttributeNS(?string $namespace, string $qualifiedName): Attr {}
/**
* @readonly
* @virtual
*/
public ?Element $firstElementChild;
/**
* @readonly
* @virtual
*/
public ?Element $lastElementChild;
/**
* @readonly
* @virtual
*/
public int $childElementCount;
/** @implementation-alias DOMDocument::getElementById */
public function getElementById(string $elementId): ?Element {}

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 1c8b81daeaf360b0ecab9ebbdf4f8865f521f43d */
* Stub hash: 2119512797f6d51d9835660cd0eccd3ba83417a9 */
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)
@ -3025,31 +3025,12 @@ static zend_class_entry *register_class_Dom_Element(zend_class_entry *class_entr
zend_declare_typed_property(class_entry, property_tagName_name, &property_tagName_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
zend_string_release(property_tagName_name);
zval property_id_default_value;
ZVAL_UNDEF(&property_id_default_value);
zend_string *property_id_name = zend_string_init("id", sizeof("id") - 1, 1);
zend_declare_typed_property(class_entry, property_id_name, &property_id_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
zend_string_release(property_id_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|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
zend_string_release(property_className_name);
zval property_classList_default_value;
ZVAL_UNDEF(&property_classList_default_value);
zend_string *property_classList_name = zend_string_init("classList", sizeof("classList") - 1, 1);
zend_string *property_classList_class_Dom_TokenList = zend_string_init("Dom\\TokenList", sizeof("Dom\\TokenList")-1, 1);
zend_declare_typed_property(class_entry, property_classList_name, &property_classList_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_classList_class_Dom_TokenList, 0, 0));
zend_string_release(property_classList_name);
zval property_attributes_default_value;
ZVAL_UNDEF(&property_attributes_default_value);
zend_string *property_attributes_name = zend_string_init("attributes", sizeof("attributes") - 1, 1);
zend_string *property_attributes_class_Dom_NamedNodeMap = zend_string_init("Dom\\\116amedNodeMap", sizeof("Dom\\\116amedNodeMap")-1, 1);
zend_declare_typed_property(class_entry, property_attributes_name, &property_attributes_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_attributes_class_Dom_NamedNodeMap, 0, 0));
zend_string_release(property_attributes_name);
zval property_children_default_value;
ZVAL_UNDEF(&property_children_default_value);
zend_string *property_children_name = zend_string_init("children", sizeof("children") - 1, 1);
zend_string *property_children_class_Dom_HTMLCollection = zend_string_init("Dom\\HTMLCollection", sizeof("Dom\\HTMLCollection")-1, 1);
zend_declare_typed_property(class_entry, property_children_name, &property_children_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_children_class_Dom_HTMLCollection, 0, 0));
zend_string_release(property_children_name);
zval property_firstElementChild_default_value;
ZVAL_UNDEF(&property_firstElementChild_default_value);
@ -3085,6 +3066,32 @@ static zend_class_entry *register_class_Dom_Element(zend_class_entry *class_entr
zend_declare_typed_property(class_entry, property_nextElementSibling_name, &property_nextElementSibling_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_nextElementSibling_class_Dom_Element, 0, MAY_BE_NULL));
zend_string_release(property_nextElementSibling_name);
zval property_id_default_value;
ZVAL_UNDEF(&property_id_default_value);
zend_string *property_id_name = zend_string_init("id", sizeof("id") - 1, 1);
zend_declare_typed_property(class_entry, property_id_name, &property_id_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
zend_string_release(property_id_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|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
zend_string_release(property_className_name);
zval property_classList_default_value;
ZVAL_UNDEF(&property_classList_default_value);
zend_string *property_classList_name = zend_string_init("classList", sizeof("classList") - 1, 1);
zend_string *property_classList_class_Dom_TokenList = zend_string_init("Dom\\TokenList", sizeof("Dom\\TokenList")-1, 1);
zend_declare_typed_property(class_entry, property_classList_name, &property_classList_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_classList_class_Dom_TokenList, 0, 0));
zend_string_release(property_classList_name);
zval property_attributes_default_value;
ZVAL_UNDEF(&property_attributes_default_value);
zend_string *property_attributes_name = zend_string_init("attributes", sizeof("attributes") - 1, 1);
zend_string *property_attributes_class_Dom_NamedNodeMap = zend_string_init("Dom\\\116amedNodeMap", sizeof("Dom\\\116amedNodeMap")-1, 1);
zend_declare_typed_property(class_entry, property_attributes_name, &property_attributes_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_attributes_class_Dom_NamedNodeMap, 0, 0));
zend_string_release(property_attributes_name);
zval property_innerHTML_default_value;
ZVAL_UNDEF(&property_innerHTML_default_value);
zend_string *property_innerHTML_name = zend_string_init("innerHTML", sizeof("innerHTML") - 1, 1);
@ -3309,6 +3316,13 @@ static zend_class_entry *register_class_Dom_DocumentFragment(zend_class_entry *c
class_entry = zend_register_internal_class_with_flags(&ce, class_entry_Dom_Node, 0);
zend_class_implements(class_entry, 1, class_entry_Dom_ParentNode);
zval property_children_default_value;
ZVAL_UNDEF(&property_children_default_value);
zend_string *property_children_name = zend_string_init("children", sizeof("children") - 1, 1);
zend_string *property_children_class_Dom_HTMLCollection = zend_string_init("Dom\\HTMLCollection", sizeof("Dom\\HTMLCollection")-1, 1);
zend_declare_typed_property(class_entry, property_children_name, &property_children_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_children_class_Dom_HTMLCollection, 0, 0));
zend_string_release(property_children_name);
zval property_firstElementChild_default_value;
ZVAL_UNDEF(&property_firstElementChild_default_value);
zend_string *property_firstElementChild_name = zend_string_init("firstElementChild", sizeof("firstElementChild") - 1, 1);
@ -3400,6 +3414,33 @@ static zend_class_entry *register_class_Dom_Document(zend_class_entry *class_ent
class_entry = zend_register_internal_class_with_flags(&ce, class_entry_Dom_Node, ZEND_ACC_ABSTRACT);
zend_class_implements(class_entry, 1, class_entry_Dom_ParentNode);
zval property_children_default_value;
ZVAL_UNDEF(&property_children_default_value);
zend_string *property_children_name = zend_string_init("children", sizeof("children") - 1, 1);
zend_string *property_children_class_Dom_HTMLCollection = zend_string_init("Dom\\HTMLCollection", sizeof("Dom\\HTMLCollection")-1, 1);
zend_declare_typed_property(class_entry, property_children_name, &property_children_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_children_class_Dom_HTMLCollection, 0, 0));
zend_string_release(property_children_name);
zval property_firstElementChild_default_value;
ZVAL_UNDEF(&property_firstElementChild_default_value);
zend_string *property_firstElementChild_name = zend_string_init("firstElementChild", sizeof("firstElementChild") - 1, 1);
zend_string *property_firstElementChild_class_Dom_Element = zend_string_init("Dom\\Element", sizeof("Dom\\Element")-1, 1);
zend_declare_typed_property(class_entry, property_firstElementChild_name, &property_firstElementChild_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_firstElementChild_class_Dom_Element, 0, MAY_BE_NULL));
zend_string_release(property_firstElementChild_name);
zval property_lastElementChild_default_value;
ZVAL_UNDEF(&property_lastElementChild_default_value);
zend_string *property_lastElementChild_name = zend_string_init("lastElementChild", sizeof("lastElementChild") - 1, 1);
zend_string *property_lastElementChild_class_Dom_Element = zend_string_init("Dom\\Element", sizeof("Dom\\Element")-1, 1);
zend_declare_typed_property(class_entry, property_lastElementChild_name, &property_lastElementChild_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_lastElementChild_class_Dom_Element, 0, MAY_BE_NULL));
zend_string_release(property_lastElementChild_name);
zval property_childElementCount_default_value;
ZVAL_UNDEF(&property_childElementCount_default_value);
zend_string *property_childElementCount_name = zend_string_init("childElementCount", sizeof("childElementCount") - 1, 1);
zend_declare_typed_property(class_entry, property_childElementCount_name, &property_childElementCount_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(property_childElementCount_name);
zval property_implementation_default_value;
ZVAL_UNDEF(&property_implementation_default_value);
zend_string *property_implementation_name = zend_string_init("implementation", sizeof("implementation") - 1, 1);
@ -3451,26 +3492,6 @@ static zend_class_entry *register_class_Dom_Document(zend_class_entry *class_ent
zend_declare_typed_property(class_entry, property_documentElement_name, &property_documentElement_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_documentElement_class_Dom_Element, 0, MAY_BE_NULL));
zend_string_release(property_documentElement_name);
zval property_firstElementChild_default_value;
ZVAL_UNDEF(&property_firstElementChild_default_value);
zend_string *property_firstElementChild_name = zend_string_init("firstElementChild", sizeof("firstElementChild") - 1, 1);
zend_string *property_firstElementChild_class_Dom_Element = zend_string_init("Dom\\Element", sizeof("Dom\\Element")-1, 1);
zend_declare_typed_property(class_entry, property_firstElementChild_name, &property_firstElementChild_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_firstElementChild_class_Dom_Element, 0, MAY_BE_NULL));
zend_string_release(property_firstElementChild_name);
zval property_lastElementChild_default_value;
ZVAL_UNDEF(&property_lastElementChild_default_value);
zend_string *property_lastElementChild_name = zend_string_init("lastElementChild", sizeof("lastElementChild") - 1, 1);
zend_string *property_lastElementChild_class_Dom_Element = zend_string_init("Dom\\Element", sizeof("Dom\\Element")-1, 1);
zend_declare_typed_property(class_entry, property_lastElementChild_name, &property_lastElementChild_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_lastElementChild_class_Dom_Element, 0, MAY_BE_NULL));
zend_string_release(property_lastElementChild_name);
zval property_childElementCount_default_value;
ZVAL_UNDEF(&property_childElementCount_default_value);
zend_string *property_childElementCount_name = zend_string_init("childElementCount", sizeof("childElementCount") - 1, 1);
zend_declare_typed_property(class_entry, property_childElementCount_name, &property_childElementCount_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(property_childElementCount_name);
zval property_body_default_value;
ZVAL_UNDEF(&property_body_default_value);
zend_string *property_body_name = zend_string_init("body", sizeof("body") - 1, 1);

View file

@ -11,7 +11,7 @@ $dom = new DomDocument();
var_dump($element);
?>
--EXPECT--
object(Dom\HTMLElement)#3 (30) {
object(Dom\HTMLElement)#3 (31) {
["namespaceURI"]=>
string(28) "http://www.w3.org/1999/xhtml"
["prefix"]=>
@ -28,6 +28,8 @@ object(Dom\HTMLElement)#3 (30) {
string(22) "(object value omitted)"
["attributes"]=>
string(22) "(object value omitted)"
["children"]=>
string(22) "(object value omitted)"
["firstElementChild"]=>
string(22) "(object value omitted)"
["lastElementChild"]=>

View file

@ -13,7 +13,7 @@ var_dump($e1, $e2);
?>
--EXPECT--
object(Dom\Element)#3 (30) {
object(Dom\Element)#3 (31) {
["namespaceURI"]=>
string(12) "urn:example1"
["prefix"]=>
@ -30,6 +30,8 @@ object(Dom\Element)#3 (30) {
string(22) "(object value omitted)"
["attributes"]=>
string(22) "(object value omitted)"
["children"]=>
string(22) "(object value omitted)"
["firstElementChild"]=>
NULL
["lastElementChild"]=>
@ -75,7 +77,7 @@ object(Dom\Element)#3 (30) {
["textContent"]=>
string(0) ""
}
object(Dom\Element)#4 (30) {
object(Dom\Element)#4 (31) {
["namespaceURI"]=>
string(12) "urn:example2"
["prefix"]=>
@ -92,6 +94,8 @@ object(Dom\Element)#4 (30) {
string(22) "(object value omitted)"
["attributes"]=>
string(22) "(object value omitted)"
["children"]=>
string(22) "(object value omitted)"
["firstElementChild"]=>
NULL
["lastElementChild"]=>

View file

@ -0,0 +1,48 @@
--TEST--
ParentNode::$children
--EXTENSIONS--
dom
--FILE--
<?php
$dom = Dom\XMLDocument::createFromString(<<<XML
<root>
<a/>
<b/>
<c id="foo"><c1/><!-- comment --><c2/></c>
<?garbage ?>
<e/>
</root>
XML);
$children = $dom->documentElement->children;
var_dump($children === $dom->documentElement->children); // Tests caching behaviour
var_dump($children !== (clone $dom->documentElement)->children); // Tests caching behaviour does not persist across clones
var_dump(count($children));
var_dump($children->length);
foreach ($children as $key => $child) {
var_dump($key, $child->nodeName);
}
foreach ($children->namedItem('foo')->children as $key => $child) {
var_dump($key, $child->nodeName);
}
?>
--EXPECT--
bool(true)
bool(true)
int(4)
int(4)
int(0)
string(1) "a"
int(1)
string(1) "b"
int(2)
string(1) "c"
int(3)
string(1) "e"
int(0)
string(2) "c1"
int(1)
string(2) "c2"

View file

@ -23,7 +23,7 @@ var_dump(get_class($dom->getElementsByTagName("p")->item(0)));
?>
--EXPECT--
object(Dom\HTMLDocument)#1 (28) {
object(Dom\HTMLDocument)#1 (29) {
["implementation"]=>
string(22) "(object value omitted)"
["URL"]=>
@ -40,6 +40,8 @@ object(Dom\HTMLDocument)#1 (28) {
NULL
["documentElement"]=>
string(22) "(object value omitted)"
["children"]=>
string(22) "(object value omitted)"
["firstElementChild"]=>
string(22) "(object value omitted)"
["lastElementChild"]=>

View file

@ -23,7 +23,7 @@ var_dump(get_class($dom->getElementsByTagName("p")->item(0)));
?>
--EXPECT--
object(Dom\HTMLDocument)#1 (28) {
object(Dom\HTMLDocument)#1 (29) {
["implementation"]=>
string(22) "(object value omitted)"
["URL"]=>
@ -40,6 +40,8 @@ object(Dom\HTMLDocument)#1 (28) {
NULL
["documentElement"]=>
string(22) "(object value omitted)"
["children"]=>
string(22) "(object value omitted)"
["firstElementChild"]=>
string(22) "(object value omitted)"
["lastElementChild"]=>

View file

@ -37,7 +37,7 @@ echo $dom->implementation->createDocument(null, "", $dtd)->saveXml(), "\n";
?>
--EXPECT--
--- (null, "") ---
object(Dom\XMLDocument)#3 (32) {
object(Dom\XMLDocument)#3 (33) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@ -62,6 +62,8 @@ object(Dom\XMLDocument)#3 (32) {
NULL
["documentElement"]=>
NULL
["children"]=>
string(22) "(object value omitted)"
["firstElementChild"]=>
NULL
["lastElementChild"]=>

View file

@ -18,7 +18,9 @@ var_dump($element->parentNode);
?>
--EXPECT--
Exception: Cannot have more than one element child in a document
object(Dom\DocumentFragment)#2 (17) {
object(Dom\DocumentFragment)#2 (18) {
["children"]=>
string(22) "(object value omitted)"
["firstElementChild"]=>
string(22) "(object value omitted)"
["lastElementChild"]=>

View file

@ -10,7 +10,7 @@ var_dump($dom);
?>
--EXPECT--
object(Dom\XMLDocument)#1 (32) {
object(Dom\XMLDocument)#1 (33) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@ -35,6 +35,8 @@ object(Dom\XMLDocument)#1 (32) {
NULL
["documentElement"]=>
NULL
["children"]=>
string(22) "(object value omitted)"
["firstElementChild"]=>
NULL
["lastElementChild"]=>

View file

@ -10,7 +10,7 @@ var_dump($dom);
?>
--EXPECT--
object(Dom\XMLDocument)#1 (32) {
object(Dom\XMLDocument)#1 (33) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@ -35,6 +35,8 @@ object(Dom\XMLDocument)#1 (32) {
NULL
["documentElement"]=>
NULL
["children"]=>
string(22) "(object value omitted)"
["firstElementChild"]=>
NULL
["lastElementChild"]=>

View file

@ -13,7 +13,7 @@ var_dump($element->ownerDocument);
?>
--EXPECTF--
object(Dom\XMLDocument)#1 (32) {
object(Dom\XMLDocument)#1 (33) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@ -38,6 +38,8 @@ object(Dom\XMLDocument)#1 (32) {
NULL
["documentElement"]=>
string(22) "(object value omitted)"
["children"]=>
string(22) "(object value omitted)"
["firstElementChild"]=>
string(22) "(object value omitted)"
["lastElementChild"]=>