Refactor implementation of DOM nodelists, named maps, and iterators

The code was really messy with lots of checks and inconsistencies.
This splits everything up into different functions and now everything is
relayed to a handler vtable.
This commit is contained in:
Niels Dossche 2025-06-21 15:37:45 +02:00
parent 4fadf647d2
commit ff0a2cff05
16 changed files with 521 additions and 421 deletions

View file

@ -33,6 +33,7 @@ if test "$PHP_DOM" != "no"; then
node.c
nodelist.c
notation.c
obj_map.c
parentnode/css_selectors.c
parentnode/tree.c
php_dom.c

View file

@ -15,7 +15,7 @@ if (PHP_DOM == "yes") {
entity.c nodelist.c html_collection.c text.c comment.c \
entityreference.c \
token_list.c \
notation.c xpath.c dom_iterators.c \
notation.c obj_map.c xpath.c dom_iterators.c \
namednodemap.c xpath_callbacks.c", null, "/I ext/lexbor");
ADD_EXTENSION_DEP('dom', 'lexbor');

View file

@ -53,7 +53,7 @@ zend_result dom_documenttype_entities_read(dom_object *obj, zval *retval)
xmlHashTable *entityht = (xmlHashTable *) dtdptr->entities;
dom_object *intern = Z_DOMOBJ_P(retval);
dom_namednode_iter(obj, XML_ENTITY_NODE, intern, entityht, NULL, NULL);
dom_namednode_iter(obj, intern, entityht, NULL, NULL, &php_dom_obj_map_entities);
return SUCCESS;
}
@ -74,7 +74,7 @@ zend_result dom_documenttype_notations_read(dom_object *obj, zval *retval)
xmlHashTable *notationht = (xmlHashTable *) dtdptr->notations;
dom_object *intern = Z_DOMOBJ_P(retval);
dom_namednode_iter(obj, XML_NOTATION_NODE, intern, notationht, NULL, NULL);
dom_namednode_iter(obj, intern, notationht, NULL, NULL, &php_dom_obj_map_notations);
return SUCCESS;
}

View file

@ -68,7 +68,7 @@ xmlNodePtr create_notation(const xmlChar *name, const xmlChar *ExternalID, const
}
/* }}} */
static xmlNode *php_dom_libxml_hash_iter_ex(xmlHashTable *ht, int index)
xmlNodePtr php_dom_libxml_hash_iter(xmlHashTable *ht, int index)
{
int htsize;
@ -84,18 +84,6 @@ static xmlNode *php_dom_libxml_hash_iter_ex(xmlHashTable *ht, int index)
}
}
xmlNode *php_dom_libxml_hash_iter(dom_nnodemap_object *objmap, int index)
{
xmlNode *curnode = php_dom_libxml_hash_iter_ex(objmap->ht, index);
if (curnode != NULL && objmap->nodetype != XML_ENTITY_NODE) {
xmlNotation *notation = (xmlNotation *) curnode;
curnode = create_notation(notation->name, notation->PublicID, notation->SystemID);
}
return curnode;
}
static void php_dom_iterator_dtor(zend_object_iterator *iter) /* {{{ */
{
php_dom_iterator *iterator = (php_dom_iterator *)iter;
@ -109,7 +97,7 @@ static zend_result php_dom_iterator_valid(zend_object_iterator *iter) /* {{{ */
{
php_dom_iterator *iterator = (php_dom_iterator *)iter;
if (Z_TYPE(iterator->curobj) != IS_UNDEF) {
if (!Z_ISNULL(iterator->curobj)) {
return SUCCESS;
} else {
return FAILURE;
@ -120,7 +108,7 @@ static zend_result php_dom_iterator_valid(zend_object_iterator *iter) /* {{{ */
zval *php_dom_iterator_current_data(zend_object_iterator *iter) /* {{{ */
{
php_dom_iterator *iterator = (php_dom_iterator *)iter;
return Z_ISUNDEF(iterator->curobj) ? NULL : &iterator->curobj;
return Z_ISNULL(iterator->curobj) ? NULL : &iterator->curobj;
}
/* }}} */
@ -131,14 +119,14 @@ static void php_dom_iterator_current_key(zend_object_iterator *iter, zval *key)
/* Only dtd named node maps, i.e. the ones based on a libxml hash table or attribute collections,
* are keyed by the name because in that case the name is unique. */
if (!objmap->ht && objmap->nodetype != XML_ATTRIBUTE_NODE) {
if (objmap->handler->nameless) {
ZVAL_LONG(key, iterator->index);
} else {
dom_object *intern = Z_DOMOBJ_P(&iterator->curobj);
if (intern != NULL && intern->ptr != NULL) {
if (intern->ptr != NULL) {
xmlNodePtr curnode = ((php_libxml_node_ptr *)intern->ptr)->node;
if (objmap->nodetype == XML_ATTRIBUTE_NODE && php_dom_follow_spec_intern(intern)) {
if (curnode->type == XML_ATTRIBUTE_NODE && php_dom_follow_spec_intern(intern)) {
ZVAL_NEW_STR(key, dom_node_get_node_name_attribute_or_element(curnode, false));
} else {
ZVAL_STRINGL(key, (const char *) curnode->name, xmlStrlen(curnode->name));
@ -150,99 +138,36 @@ static void php_dom_iterator_current_key(zend_object_iterator *iter, zval *key)
}
/* }}} */
static xmlNodePtr dom_fetch_first_iteration_item(dom_nnodemap_object *objmap)
{
xmlNodePtr basep = dom_object_get_node(objmap->baseobj);
if (!basep) {
return NULL;
}
if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) {
if (objmap->nodetype == XML_ATTRIBUTE_NODE) {
return (xmlNodePtr) basep->properties;
} else {
return dom_nodelist_iter_start_first_child(basep);
}
} else {
zend_long curindex = 0;
xmlNodePtr nodep = php_dom_first_child_of_container_node(basep);
return dom_get_elements_by_tag_name_ns_raw(
basep, nodep, objmap->ns, objmap->local, objmap->local_lower, &curindex, 0);
}
}
static void php_dom_iterator_move_forward(zend_object_iterator *iter) /* {{{ */
{
xmlNodePtr curnode = NULL;
php_dom_iterator *iterator = (php_dom_iterator *)iter;
if (Z_ISUNDEF(iterator->curobj)) {
if (Z_ISNULL(iterator->curobj)) {
return;
}
iterator->index++;
zval garbage;
ZVAL_COPY_VALUE(&garbage, &iterator->curobj);
ZVAL_NULL(&iterator->curobj);
dom_object *intern = Z_DOMOBJ_P(&iterator->curobj);
dom_nnodemap_object *objmap = php_dom_iterator_get_nnmap(iterator);
if (intern != NULL && intern->ptr != NULL) {
if (objmap->nodetype != XML_ENTITY_NODE &&
objmap->nodetype != XML_NOTATION_NODE) {
if (objmap->nodetype == DOM_NODESET) {
HashTable *nodeht = Z_ARRVAL_P(&objmap->baseobj_zv);
zval *entry = zend_hash_index_find(nodeht, iterator->index);
if (entry) {
zval_ptr_dtor(&iterator->curobj);
ZVAL_COPY(&iterator->curobj, entry);
return;
}
} else {
if (objmap->nodetype == XML_ATTRIBUTE_NODE ||
objmap->nodetype == XML_ELEMENT_NODE) {
/* Note: keep legacy behaviour for non-spec mode. */
if (php_dom_follow_spec_intern(intern) && php_dom_is_cache_tag_stale_from_doc_ptr(&iterator->cache_tag, intern->document)) {
php_dom_mark_cache_tag_up_to_date_from_doc_ref(&iterator->cache_tag, intern->document);
curnode = dom_fetch_first_iteration_item(objmap);
zend_ulong index = 0;
while (curnode != NULL && index++ < iterator->index) {
curnode = curnode->next;
}
} else {
curnode = (xmlNodePtr)((php_libxml_node_ptr *)intern->ptr)->node;
curnode = curnode->next;
}
} else {
/* The collection is live, we nav the tree from the base object if we cannot
* use the cache to restart from the last point. */
xmlNodePtr basenode = dom_object_get_node(objmap->baseobj);
/* We have a strong reference to the base node via baseobj_zv, this cannot become NULL */
ZEND_ASSERT(basenode != NULL);
zend_long previndex;
if (php_dom_is_cache_tag_stale_from_node(&iterator->cache_tag, basenode)) {
php_dom_mark_cache_tag_up_to_date_from_node(&iterator->cache_tag, basenode);
previndex = 0;
curnode = php_dom_first_child_of_container_node(basenode);
} else {
previndex = iterator->index - 1;
curnode = (xmlNodePtr)((php_libxml_node_ptr *)intern->ptr)->node;
}
curnode = dom_get_elements_by_tag_name_ns_raw(
basenode, curnode, objmap->ns, objmap->local, objmap->local_lower, &previndex, iterator->index);
}
if (intern->ptr != NULL) {
/* Note: keep legacy behaviour for non-spec mode. */
/* TODO: make this prettier */
if (!php_dom_follow_spec_intern(intern) && (objmap->handler == &php_dom_obj_map_attributes || objmap->handler == &php_dom_obj_map_child_nodes)) {
xmlNodePtr curnode = ((php_libxml_node_ptr *) intern->ptr)->node;
curnode = curnode->next;
if (curnode) {
php_dom_create_object(curnode, &iterator->curobj, objmap->baseobj);
}
} else {
curnode = php_dom_libxml_hash_iter(objmap, iterator->index);
objmap->handler->get_item(objmap, (zend_long) iterator->index, &iterator->curobj);
}
}
zval_ptr_dtor(&iterator->curobj);
ZVAL_UNDEF(&iterator->curobj);
if (curnode) {
php_dom_create_object(curnode, &iterator->curobj, objmap->baseobj);
}
zval_ptr_dtor(&garbage);
}
/* }}} */
@ -261,7 +186,6 @@ zend_object_iterator *php_dom_get_iterator(zend_class_entry *ce, zval *object, i
{
dom_object *intern;
dom_nnodemap_object *objmap;
xmlNodePtr curnode=NULL;
php_dom_iterator *iterator;
if (by_ref) {
@ -277,26 +201,7 @@ zend_object_iterator *php_dom_get_iterator(zend_class_entry *ce, zval *object, i
intern = Z_DOMOBJ_P(object);
objmap = (dom_nnodemap_object *)intern->ptr;
if (objmap != NULL) {
if (objmap->nodetype != XML_ENTITY_NODE &&
objmap->nodetype != XML_NOTATION_NODE) {
if (objmap->nodetype == DOM_NODESET) {
HashTable *nodeht = Z_ARRVAL_P(&objmap->baseobj_zv);
zval *entry = zend_hash_index_find(nodeht, 0);
if (entry) {
ZVAL_COPY(&iterator->curobj, entry);
}
} else {
curnode = dom_fetch_first_iteration_item(objmap);
}
} else {
curnode = php_dom_libxml_hash_iter(objmap, 0);
}
}
if (curnode) {
php_dom_create_object(curnode, &iterator->curobj, objmap->baseobj);
}
objmap->handler->get_item(objmap, 0, &iterator->curobj);
return &iterator->intern;
}

View file

@ -825,7 +825,7 @@ static void dom_element_get_elements_by_tag_name(INTERNAL_FUNCTION_PARAMETERS, z
object_init_ex(return_value, iter_ce);
namednode = Z_DOMOBJ_P(return_value);
dom_namednode_iter(intern, 0, namednode, NULL, name, NULL);
dom_namednode_iter(intern, namednode, NULL, name, NULL, &php_dom_obj_map_by_tag_name);
}
PHP_METHOD(DOMElement, getElementsByTagName)
@ -1257,7 +1257,7 @@ static void dom_element_get_elements_by_tag_name_ns(INTERNAL_FUNCTION_PARAMETERS
object_init_ex(return_value, iter_ce);
namednode = Z_DOMOBJ_P(return_value);
dom_namednode_iter(intern, 0, namednode, NULL, name, uri);
dom_namednode_iter(intern, namednode, NULL, name, uri, &php_dom_obj_map_by_tag_name);
}
PHP_METHOD(DOMElement, getElementsByTagNameNS)

View file

@ -33,24 +33,12 @@
zend_long php_dom_get_namednodemap_length(dom_object *obj)
{
dom_nnodemap_object *objmap = (dom_nnodemap_object *) obj->ptr;
dom_nnodemap_object *objmap = obj->ptr;
if (!objmap) {
return 0;
}
if (objmap->nodetype == XML_NOTATION_NODE || objmap->nodetype == XML_ENTITY_NODE) {
return objmap->ht ? xmlHashSize(objmap->ht) : 0;
}
zend_long count = 0;
xmlNodePtr nodep = dom_object_get_node(objmap->baseobj);
if (nodep) {
for (xmlAttrPtr curnode = nodep->properties; curnode; curnode = curnode->next) {
count++;
}
}
return count;
return objmap->handler->length(objmap);
}
/* {{{ length int
@ -66,43 +54,9 @@ zend_result dom_namednodemap_length_read(dom_object *obj, zval *retval)
/* }}} */
xmlNodePtr php_dom_named_node_map_get_named_item(dom_nnodemap_object *objmap, const zend_string *named, bool may_transform)
void php_dom_named_node_map_get_named_item_into_zval(dom_nnodemap_object *objmap, const zend_string *named, const char *ns, zval *return_value)
{
xmlNodePtr itemnode = NULL;
if (objmap != NULL) {
if ((objmap->nodetype == XML_NOTATION_NODE) ||
objmap->nodetype == XML_ENTITY_NODE) {
if (objmap->ht) {
if (objmap->nodetype == XML_ENTITY_NODE) {
itemnode = (xmlNodePtr)xmlHashLookup(objmap->ht, BAD_CAST ZSTR_VAL(named));
} else {
xmlNotationPtr notep = xmlHashLookup(objmap->ht, BAD_CAST ZSTR_VAL(named));
if (notep) {
if (may_transform) {
itemnode = create_notation(notep->name, notep->PublicID, notep->SystemID);
} else {
itemnode = (xmlNodePtr) notep;
}
}
}
}
} else {
xmlNodePtr nodep = dom_object_get_node(objmap->baseobj);
if (nodep) {
if (php_dom_follow_spec_intern(objmap->baseobj)) {
itemnode = (xmlNodePtr) php_dom_get_attribute_node(nodep, BAD_CAST ZSTR_VAL(named), ZSTR_LEN(named));
} else {
itemnode = (xmlNodePtr) xmlHasProp(nodep, BAD_CAST ZSTR_VAL(named));
}
}
}
}
return itemnode;
}
void php_dom_named_node_map_get_named_item_into_zval(dom_nnodemap_object *objmap, const zend_string *named, zval *return_value)
{
xmlNodePtr itemnode = php_dom_named_node_map_get_named_item(objmap, named, true);
xmlNodePtr itemnode = objmap->handler->get_named_item(objmap, named, ns);
if (itemnode) {
DOM_RET_OBJ(itemnode, objmap->baseobj);
} else {
@ -122,45 +76,10 @@ PHP_METHOD(DOMNamedNodeMap, getNamedItem)
}
dom_nnodemap_object *objmap = Z_DOMOBJ_P(ZEND_THIS)->ptr;
php_dom_named_node_map_get_named_item_into_zval(objmap, named, return_value);
php_dom_named_node_map_get_named_item_into_zval(objmap, named, NULL, return_value);
}
/* }}} end dom_namednodemap_get_named_item */
xmlNodePtr php_dom_named_node_map_get_item(dom_nnodemap_object *objmap, zend_long index)
{
xmlNodePtr itemnode = NULL;
if (objmap != NULL) {
if ((objmap->nodetype == XML_NOTATION_NODE) ||
objmap->nodetype == XML_ENTITY_NODE) {
if (objmap->ht) {
itemnode = php_dom_libxml_hash_iter(objmap, index);
}
} else {
xmlNodePtr nodep = dom_object_get_node(objmap->baseobj);
if (nodep) {
xmlNodePtr curnode = (xmlNodePtr)nodep->properties;
zend_long count = 0;
while (count < index && curnode != NULL) {
count++;
curnode = curnode->next;
}
itemnode = curnode;
}
}
}
return itemnode;
}
void php_dom_named_node_map_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value)
{
xmlNodePtr itemnode = php_dom_named_node_map_get_item(objmap, index);
if (itemnode) {
DOM_RET_OBJ(itemnode, objmap->baseobj);
} else {
RETURN_NULL();
}
}
/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-349467F9
Since:
*/
@ -177,7 +96,7 @@ PHP_METHOD(DOMNamedNodeMap, item)
dom_object *intern = Z_DOMOBJ_P(ZEND_THIS);
dom_nnodemap_object *objmap = intern->ptr;
php_dom_named_node_map_get_item_into_zval(objmap, index, return_value);
objmap->handler->get_item(objmap, index, return_value);
}
/* }}} end dom_namednodemap_item */
@ -186,16 +105,14 @@ Since: DOM Level 2
*/
PHP_METHOD(DOMNamedNodeMap, getNamedItemNS)
{
size_t namedlen=0, urilen=0;
size_t urilen=0;
dom_object *intern;
xmlNodePtr itemnode = NULL;
char *uri, *named;
char *uri;
zend_string *named;
dom_nnodemap_object *objmap;
xmlNodePtr nodep;
xmlNotation *notep = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!s", &uri, &urilen, &named, &namedlen) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!S", &uri, &urilen, &named) == FAILURE) {
RETURN_THROWS();
}
@ -204,28 +121,7 @@ PHP_METHOD(DOMNamedNodeMap, getNamedItemNS)
objmap = (dom_nnodemap_object *)intern->ptr;
if (objmap != NULL) {
if ((objmap->nodetype == XML_NOTATION_NODE) ||
objmap->nodetype == XML_ENTITY_NODE) {
if (objmap->ht) {
if (objmap->nodetype == XML_ENTITY_NODE) {
itemnode = (xmlNodePtr)xmlHashLookup(objmap->ht, BAD_CAST named);
} else {
notep = (xmlNotation *)xmlHashLookup(objmap->ht, BAD_CAST named);
if (notep) {
itemnode = create_notation(notep->name, notep->PublicID, notep->SystemID);
}
}
}
} else {
nodep = dom_object_get_node(objmap->baseobj);
if (nodep) {
itemnode = (xmlNodePtr)xmlHasNsProp(nodep, BAD_CAST named, BAD_CAST uri);
}
}
}
if (itemnode) {
DOM_RET_OBJ(itemnode, objmap->baseobj);
php_dom_named_node_map_get_named_item_into_zval(objmap, named, uri, return_value);
}
}
/* }}} end dom_namednodemap_get_named_item_ns */

View file

@ -288,7 +288,7 @@ zend_result dom_node_child_nodes_read(dom_object *obj, zval *retval)
object_init_ex(retval, dom_get_nodelist_ce(php_dom_follow_spec_intern(obj)));
dom_object *intern = Z_DOMOBJ_P(retval);
dom_namednode_iter(obj, XML_ELEMENT_NODE, intern, NULL, NULL, NULL);
dom_namednode_iter(obj, intern, NULL, NULL, NULL, &php_dom_obj_map_child_nodes);
return SUCCESS;
}
@ -422,7 +422,7 @@ zend_result dom_node_attributes_read(dom_object *obj, zval *retval)
if (nodep->type == XML_ELEMENT_NODE) {
object_init_ex(retval, dom_get_namednodemap_ce(php_dom_follow_spec_intern(obj)));
dom_object *intern = Z_DOMOBJ_P(retval);
dom_namednode_iter(obj, XML_ATTRIBUTE_NODE, intern, NULL, NULL, NULL);
dom_namednode_iter(obj, intern, NULL, NULL, NULL, &php_dom_obj_map_attributes);
} else {
ZVAL_NULL(retval);
}

View file

@ -32,24 +32,6 @@
* Since:
*/
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);
}
objmap->cached_obj = NULL;
objmap->cached_obj_index = 0;
}
}
static zend_always_inline void reset_objmap_cache(dom_nnodemap_object *objmap)
{
objmap_cache_release_cached_obj(objmap);
objmap->cached_length = -1;
}
xmlNodePtr dom_nodelist_iter_start_first_child(xmlNodePtr nodep)
{
if (nodep->type == XML_ENTITY_REF_NODE) {
@ -60,60 +42,6 @@ xmlNodePtr dom_nodelist_iter_start_first_child(xmlNodePtr nodep)
return nodep->children;
}
zend_long php_dom_get_nodelist_length(dom_object *obj)
{
dom_nnodemap_object *objmap = (dom_nnodemap_object *) obj->ptr;
if (!objmap) {
return 0;
}
if (objmap->ht) {
return xmlHashSize(objmap->ht);
}
if (objmap->nodetype == DOM_NODESET) {
HashTable *nodeht = Z_ARRVAL_P(&objmap->baseobj_zv);
return zend_hash_num_elements(nodeht);
}
xmlNodePtr nodep = dom_object_get_node(objmap->baseobj);
if (!nodep) {
return 0;
}
if (!php_dom_is_cache_tag_stale_from_node(&objmap->cache_tag, nodep)) {
if (objmap->cached_length >= 0) {
return objmap->cached_length;
}
/* Only the length is out-of-date, the cache tag is still valid.
* Therefore, only overwrite the length and keep the currently cached object. */
} else {
php_dom_mark_cache_tag_up_to_date_from_node(&objmap->cache_tag, nodep);
reset_objmap_cache(objmap);
}
zend_long count = 0;
if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) {
xmlNodePtr curnode = dom_nodelist_iter_start_first_child(nodep);
if (curnode) {
count++;
while (curnode->next != NULL) {
count++;
curnode = curnode->next;
}
}
} else {
xmlNodePtr basep = nodep;
nodep = php_dom_first_child_of_container_node(basep);
dom_get_elements_by_tag_name_ns_raw(
basep, nodep, objmap->ns, objmap->local, objmap->local_lower, &count, ZEND_LONG_MAX - 1 /* because of <= */);
}
objmap->cached_length = count;
return count;
}
/* {{{ length int
readonly=yes
URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-203510337
@ -137,94 +65,11 @@ PHP_METHOD(DOMNodeList, count)
void php_dom_nodelist_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value)
{
xmlNodePtr itemnode = NULL;
bool cache_itemnode = false;
if (index >= 0) {
if (objmap != NULL) {
if (objmap->ht) {
itemnode = php_dom_libxml_hash_iter(objmap, index);
} else {
if (objmap->nodetype == DOM_NODESET) {
HashTable *nodeht = Z_ARRVAL_P(&objmap->baseobj_zv);
zval *entry = zend_hash_index_find(nodeht, index);
if (entry) {
ZVAL_COPY(return_value, entry);
return;
}
} else if (objmap->baseobj) {
xmlNodePtr basep = dom_object_get_node(objmap->baseobj);
if (basep) {
xmlNodePtr nodep = basep;
/* For now we're only able to use cache for forward search.
* TODO: in the future we could extend the logic of the node list such that backwards searches
* are also possible. */
bool restart = true;
zend_long relative_index = index;
if (index >= objmap->cached_obj_index && objmap->cached_obj && !php_dom_is_cache_tag_stale_from_node(&objmap->cache_tag, nodep)) {
xmlNodePtr cached_obj_xml_node = dom_object_get_node(objmap->cached_obj);
/* The node cannot be NULL if the cache is valid. If it is NULL, then it means we
* forgot an invalidation somewhere. Take the defensive programming approach and invalidate
* it here if it's NULL (except in debug mode where we would want to catch this). */
if (UNEXPECTED(cached_obj_xml_node == NULL)) {
#if ZEND_DEBUG
ZEND_UNREACHABLE();
#endif
reset_objmap_cache(objmap);
} else {
restart = false;
relative_index -= objmap->cached_obj_index;
nodep = cached_obj_xml_node;
}
}
zend_long count = 0;
if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) {
if (restart) {
nodep = dom_nodelist_iter_start_first_child(nodep);
}
while (count < relative_index && nodep != NULL) {
count++;
nodep = nodep->next;
}
itemnode = nodep;
} else {
if (restart) {
nodep = php_dom_first_child_of_container_node(basep);
}
itemnode = dom_get_elements_by_tag_name_ns_raw(basep, nodep, objmap->ns, objmap->local, objmap->local_lower, &count, relative_index);
}
cache_itemnode = true;
}
}
}
}
if (itemnode) {
DOM_RET_OBJ(itemnode, objmap->baseobj);
if (cache_itemnode) {
/* Hold additional reference for the cache, must happen before releasing the cache
* because we might be the last reference holder.
* Instead of storing and copying zvals, we store the object pointer directly.
* This saves us some bytes because a pointer is smaller than a zval.
* This also means we have to manually refcount the objects here, and remove the reference count
* in reset_objmap_cache() and the destructor. */
dom_object *cached_obj = Z_DOMOBJ_P(return_value);
GC_ADDREF(&cached_obj->std);
/* If the tag is stale, all cached data is useless. Otherwise only the cached object is useless. */
if (php_dom_is_cache_tag_stale_from_node(&objmap->cache_tag, itemnode)) {
php_dom_mark_cache_tag_up_to_date_from_node(&objmap->cache_tag, itemnode);
reset_objmap_cache(objmap);
} else {
objmap_cache_release_cached_obj(objmap);
}
objmap->cached_obj_index = index;
objmap->cached_obj = cached_obj;
}
return;
}
if (EXPECTED(objmap)) {
objmap->handler->get_item(objmap, index, return_value);
} else {
RETURN_NULL();
}
RETVAL_NULL();
}
/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-844377136

413
ext/dom/obj_map.c Normal file
View file

@ -0,0 +1,413 @@
/*
+----------------------------------------------------------------------+
| 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: Christian Stocker <chregu@php.net> |
| Rob Richards <rrichards@php.net> |
| 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"
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);
}
objmap->cached_obj = NULL;
objmap->cached_obj_index = 0;
}
}
static zend_always_inline void reset_objmap_cache(dom_nnodemap_object *objmap)
{
objmap_cache_release_cached_obj(objmap);
objmap->cached_length = -1;
}
/**************************
* === Length methods === *
**************************/
static zend_long dom_map_get_xmlht_length(dom_nnodemap_object *map)
{
/* Note: if there are, for example, no entities or notations then the hash table can be NULL. */
return map->ht ? xmlHashSize(map->ht) : 0;
}
static zend_long dom_map_get_nodeset_length(dom_nnodemap_object *map)
{
HashTable *nodeht = Z_ARRVAL(map->baseobj_zv);
return zend_hash_num_elements(nodeht);
}
static zend_long dom_map_get_prop_length(dom_nnodemap_object *map)
{
zend_long count = 0;
xmlNodePtr nodep = dom_object_get_node(map->baseobj);
if (nodep) {
for (xmlAttrPtr curnode = nodep->properties; curnode; curnode = curnode->next) {
count++;
}
}
return count;
}
static zend_long dom_map_get_nodes_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) {
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);
zend_long count = 0;
if (nodep) {
xmlNodePtr basep = nodep;
nodep = php_dom_first_child_of_container_node(basep);
dom_get_elements_by_tag_name_ns_raw(
basep, nodep, map->ns, map->local, map->local_lower, &count, ZEND_LONG_MAX - 1 /* because of <= */);
}
return count;
}
static zend_long dom_map_get_zero_length(dom_nnodemap_object *map)
{
return 0;
}
/************************
* === Item lookups === *
************************/
static void dom_ret_node_to_zobj(dom_nnodemap_object *map, xmlNodePtr node, zval *return_value)
{
if (node) {
DOM_RET_OBJ(node, map->baseobj);
} else {
RETURN_NULL();
}
}
static void dom_map_get_entity_item(dom_nnodemap_object *map, zend_long index, zval *return_value)
{
xmlNodePtr node = map->ht ? php_dom_libxml_hash_iter(map->ht, index) : NULL;
dom_ret_node_to_zobj(map, node, return_value);
}
static void dom_map_get_notation_item(dom_nnodemap_object *map, zend_long index, zval *return_value)
{
xmlNodePtr node = map->ht ? php_dom_libxml_hash_iter(map->ht, index) : NULL;
if (node) {
xmlNotation *notation = (xmlNotation *) node;
node = create_notation(notation->name, notation->PublicID, notation->SystemID);
}
dom_ret_node_to_zobj(map, node, return_value);
}
static void dom_map_get_nodeset_item(dom_nnodemap_object *map, zend_long index, zval *return_value)
{
HashTable *nodeht = Z_ARRVAL(map->baseobj_zv);
zval *entry = zend_hash_index_find(nodeht, index);
if (entry) {
RETURN_COPY(entry);
} else {
RETURN_NULL();
}
}
typedef struct dom_node_idx_pair {
xmlNodePtr node;
zend_long index;
} dom_node_idx_pair;
static dom_node_idx_pair dom_obj_map_get_start_point(dom_nnodemap_object *map, xmlNodePtr basep, zend_long index)
{
dom_node_idx_pair ret;
ZEND_ASSERT(basep != NULL);
/* For now we're only able to use cache for forward search.
* TODO: in the future we could extend the logic of the node list such that backwards searches
* are also possible. */
bool restart = true;
zend_long relative_index = index;
if (index >= map->cached_obj_index && map->cached_obj && !php_dom_is_cache_tag_stale_from_node(&map->cache_tag, basep)) {
xmlNodePtr cached_obj_xml_node = dom_object_get_node(map->cached_obj);
/* The node cannot be NULL if the cache is valid. If it is NULL, then it means we
* forgot an invalidation somewhere. Take the defensive programming approach and invalidate
* it here if it's NULL (except in debug mode where we would want to catch this). */
if (UNEXPECTED(cached_obj_xml_node == NULL)) {
#if ZEND_DEBUG
ZEND_UNREACHABLE();
#endif
reset_objmap_cache(map);
} else {
restart = false;
relative_index -= map->cached_obj_index;
basep = cached_obj_xml_node;
}
}
ret.node = restart ? NULL : basep;
ret.index = relative_index;
return ret;
}
static void dom_map_cache_obj(dom_nnodemap_object *map, xmlNodePtr itemnode, zend_long index, zval *return_value)
{
/* Hold additional reference for the cache, must happen before releasing the cache
* because we might be the last reference holder.
* Instead of storing and copying zvals, we store the object pointer directly.
* This saves us some bytes because a pointer is smaller than a zval.
* This also means we have to manually refcount the objects here, and remove the reference count
* in reset_objmap_cache() and the destructor. */
dom_object *cached_obj = Z_DOMOBJ_P(return_value);
GC_ADDREF(&cached_obj->std);
/* If the tag is stale, all cached data is useless. Otherwise only the cached object is useless. */
if (php_dom_is_cache_tag_stale_from_node(&map->cache_tag, itemnode)) {
php_dom_mark_cache_tag_up_to_date_from_node(&map->cache_tag, itemnode);
reset_objmap_cache(map);
} else {
objmap_cache_release_cached_obj(map);
}
map->cached_obj_index = index;
map->cached_obj = cached_obj;
}
static xmlNodePtr dom_map_get_attr_start(xmlNodePtr node)
{
ZEND_ASSERT(node->type == XML_ELEMENT_NODE);
return (xmlNodePtr) node->properties;
}
static void dom_map_get_chain_item(dom_nnodemap_object *map, zend_long index, zval *return_value, xmlNodePtr (*get_start)(xmlNodePtr))
{
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);
itemnode = start_point.node ? start_point.node : get_start(nodep);
for (; start_point.index > 0 && itemnode; itemnode = itemnode->next, start_point.index--);
}
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_attributes_item(dom_nnodemap_object *map, zend_long index, zval *return_value)
{
dom_map_get_chain_item(map, index, return_value, dom_map_get_attr_start);
}
static void dom_map_get_nodes_item(dom_nnodemap_object *map, zend_long index, zval *return_value)
{
dom_map_get_chain_item(map, index, return_value, dom_nodelist_iter_start_first_child);
}
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);
xmlNodePtr itemnode = NULL;
if (nodep && index >= 0) {
zend_long count = 0;
dom_node_idx_pair start_point = dom_obj_map_get_start_point(map, nodep, index);
itemnode = start_point.node ? start_point.node : php_dom_first_child_of_container_node(nodep);
itemnode = dom_get_elements_by_tag_name_ns_raw(nodep, itemnode, map->ns, map->local, map->local_lower, &count, start_point.index);
}
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_null_item(dom_nnodemap_object *map, zend_long index, zval *return_value)
{
RETURN_NULL();
}
/***********************
* === Common APIs === *
***********************/
zend_long php_dom_get_nodelist_length(dom_object *obj)
{
dom_nnodemap_object *objmap = obj->ptr;
if (!objmap) {
return 0;
}
if (objmap->handler->use_cache) {
xmlNodePtr nodep = dom_object_get_node(objmap->baseobj);
if (!nodep) {
return 0;
}
if (!php_dom_is_cache_tag_stale_from_node(&objmap->cache_tag, nodep)) {
if (objmap->cached_length >= 0) {
return objmap->cached_length;
}
/* Only the length is out-of-date, the cache tag is still valid.
* Therefore, only overwrite the length and keep the currently cached object. */
} else {
php_dom_mark_cache_tag_up_to_date_from_node(&objmap->cache_tag, nodep);
reset_objmap_cache(objmap);
}
}
zend_long count = objmap->handler->length(objmap);
if (objmap->handler->use_cache) {
objmap->cached_length = count;
}
return count;
}
/**********************
* === Named item === *
**********************/
static xmlNodePtr dom_map_get_named_item_entity(dom_nnodemap_object *map, const zend_string *named, const char *ns)
{
return xmlHashLookup(map->ht, BAD_CAST ZSTR_VAL(named));
}
static bool dom_map_has_named_item_xmlht(dom_nnodemap_object *map, const zend_string *named, const char *ns)
{
return dom_map_get_named_item_entity(map, named, ns) != NULL;
}
static xmlNodePtr dom_map_get_named_item_notation(dom_nnodemap_object *map, const zend_string *named, const char *ns)
{
xmlNotationPtr notation = xmlHashLookup(map->ht, BAD_CAST ZSTR_VAL(named));
if (notation) {
return create_notation(notation->name, notation->PublicID, notation->SystemID);
}
return NULL;
}
static xmlNodePtr dom_map_get_named_item_prop(dom_nnodemap_object *map, const zend_string *named, const char *ns)
{
xmlNodePtr nodep = dom_object_get_node(map->baseobj);
if (nodep) {
if (php_dom_follow_spec_intern(map->baseobj)) {
return (xmlNodePtr) php_dom_get_attribute_node(nodep, BAD_CAST ZSTR_VAL(named), ZSTR_LEN(named));
} else {
if (ns) {
return (xmlNodePtr) xmlHasNsProp(nodep, BAD_CAST ZSTR_VAL(named), BAD_CAST ns);
} else {
return (xmlNodePtr) xmlHasProp(nodep, BAD_CAST ZSTR_VAL(named));
}
}
}
return NULL;
}
static bool dom_map_has_named_item_prop(dom_nnodemap_object *map, const zend_string *named, const char *ns)
{
return dom_map_get_named_item_prop(map, named, ns) != NULL;
}
static xmlNodePtr dom_map_get_named_item_null(dom_nnodemap_object *map, const zend_string *named, const char *ns)
{
return NULL;
}
static bool dom_map_has_named_item_null(dom_nnodemap_object *map, const zend_string *named, const char *ns)
{
return false;
}
/**************************
* === Handler tables === *
**************************/
const php_dom_obj_map_handler php_dom_obj_map_attributes = {
.length = dom_map_get_prop_length,
.get_item = dom_map_get_attributes_item,
.get_named_item = dom_map_get_named_item_prop,
.has_named_item = dom_map_has_named_item_prop,
.use_cache = true,
.nameless = false,
};
const php_dom_obj_map_handler php_dom_obj_map_by_tag_name = {
.length = dom_map_get_by_tag_name_length,
.get_item = dom_map_get_by_tag_name_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_child_nodes = {
.length = dom_map_get_nodes_length,
.get_item = dom_map_get_nodes_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_nodeset = {
.length = dom_map_get_nodeset_length,
.get_item = dom_map_get_nodeset_item,
.get_named_item = dom_map_get_named_item_null,
.has_named_item = dom_map_has_named_item_null,
.use_cache = false,
.nameless = true,
};
const php_dom_obj_map_handler php_dom_obj_map_entities = {
.length = dom_map_get_xmlht_length,
.get_item = dom_map_get_entity_item,
.get_named_item = dom_map_get_named_item_entity,
.has_named_item = dom_map_has_named_item_xmlht,
.use_cache = false,
.nameless = false,
};
const php_dom_obj_map_handler php_dom_obj_map_notations = {
.length = dom_map_get_xmlht_length,
.get_item = dom_map_get_notation_item,
.get_named_item = dom_map_get_named_item_notation,
.has_named_item = dom_map_has_named_item_xmlht,
.use_cache = false,
.nameless = false,
};
const php_dom_obj_map_handler php_dom_obj_map_noop = {
.length = dom_map_get_zero_length,
.get_item = dom_map_get_null_item,
.get_named_item = dom_map_get_named_item_null,
.has_named_item = dom_map_has_named_item_null,
.use_cache = false,
.nameless = true,
};
#endif

39
ext/dom/obj_map.h Normal file
View file

@ -0,0 +1,39 @@
/*
+----------------------------------------------------------------------+
| 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> |
+----------------------------------------------------------------------+
*/
#ifndef PHP_DOM_OBJ_MAP_H
#define PHP_DOM_OBJ_MAP_H
typedef struct dom_nnodemap_object dom_nnodemap_object;
typedef struct php_dom_obj_map_handler {
zend_long (*length)(dom_nnodemap_object *);
void (*get_item)(dom_nnodemap_object *, zend_long, zval *);
xmlNodePtr (*get_named_item)(dom_nnodemap_object *, const zend_string *, const char *);
bool (*has_named_item)(dom_nnodemap_object *, const zend_string *, const char *);
bool use_cache;
bool nameless;
} php_dom_obj_map_handler;
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_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;
extern const php_dom_obj_map_handler php_dom_obj_map_notations;
extern const php_dom_obj_map_handler php_dom_obj_map_noop;
#endif

View file

@ -249,7 +249,7 @@ void dom_parent_node_query_selector_all(xmlNodePtr thisp, dom_object *intern, zv
dom_object *ret_obj = Z_DOMOBJ_P(return_value);
dom_nnodemap_object *mapptr = (dom_nnodemap_object *) ret_obj->ptr;
ZVAL_ARR(&mapptr->baseobj_zv, list);
mapptr->nodetype = DOM_NODESET;
mapptr->handler = &php_dom_obj_map_nodeset;
}
}

View file

@ -1456,7 +1456,8 @@ void dom_objects_free_storage(zend_object *object)
}
/* }}} */
void dom_namednode_iter(dom_object *basenode, int ntype, dom_object *intern, xmlHashTablePtr ht, zend_string *local, zend_string *ns) /* {{{ */
/* TODO: move me to obj_map.c */
void dom_namednode_iter(dom_object *basenode, dom_object *intern, xmlHashTablePtr ht, zend_string *local, zend_string *ns, const php_dom_obj_map_handler *handler) /* {{{ */
{
dom_nnodemap_object *mapptr = (dom_nnodemap_object *) intern->ptr;
@ -1466,8 +1467,8 @@ void dom_namednode_iter(dom_object *basenode, int ntype, dom_object *intern, xml
xmlDocPtr doc = basenode->document ? basenode->document->ptr : NULL;
mapptr->handler = handler;
mapptr->baseobj = basenode;
mapptr->nodetype = ntype;
mapptr->ht = ht;
if (EXPECTED(doc != NULL)) {
mapptr->dict = doc->dict;
@ -1607,6 +1608,7 @@ zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type)
intern->ptr = ecalloc(1, sizeof(dom_nnodemap_object));
dom_nnodemap_object *objmap = intern->ptr;
objmap->cached_length = -1;
objmap->handler = &php_dom_obj_map_noop;
return &intern->std;
}
@ -2380,7 +2382,7 @@ static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int t
zend_long lval;
if (dom_nodemap_or_nodelist_process_offset_as_named(offset, &lval)) {
/* exceptional case, switch to named lookup */
php_dom_named_node_map_get_named_item_into_zval(php_dom_obj_from_obj(object)->ptr, Z_STR_P(offset), rv);
php_dom_named_node_map_get_named_item_into_zval(php_dom_obj_from_obj(object)->ptr, Z_STR_P(offset), NULL, rv);
return rv;
}
@ -2390,7 +2392,8 @@ static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int t
return NULL;
}
php_dom_named_node_map_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, lval, rv);
dom_nnodemap_object *map = php_dom_obj_from_obj(object)->ptr;
map->handler->get_item(map, lval, rv);
return rv;
}
@ -2404,7 +2407,8 @@ static int dom_nodemap_has_dimension(zend_object *object, zval *member, int chec
zend_long offset;
if (dom_nodemap_or_nodelist_process_offset_as_named(member, &offset)) {
/* exceptional case, switch to named lookup */
return php_dom_named_node_map_get_named_item(php_dom_obj_from_obj(object)->ptr, Z_STR_P(member), false) != NULL;
dom_nnodemap_object *map = php_dom_obj_from_obj(object)->ptr;
return map->handler->has_named_item(map, Z_STR_P(member), NULL);
}
return offset >= 0 && offset < php_dom_get_namednodemap_length(php_dom_obj_from_obj(object));
@ -2423,14 +2427,14 @@ static zval *dom_modern_nodemap_read_dimension(zend_object *object, zval *offset
if (Z_TYPE_P(offset) == IS_STRING) {
zend_ulong lval;
if (ZEND_HANDLE_NUMERIC(Z_STR_P(offset), lval)) {
php_dom_named_node_map_get_item_into_zval(map, (zend_long) lval, rv);
map->handler->get_item(map, (zend_long) lval, rv);
} else {
php_dom_named_node_map_get_named_item_into_zval(map, Z_STR_P(offset), rv);
php_dom_named_node_map_get_named_item_into_zval(map, Z_STR_P(offset), NULL, rv);
}
} else if (Z_TYPE_P(offset) == IS_LONG) {
php_dom_named_node_map_get_item_into_zval(map, Z_LVAL_P(offset), rv);
map->handler->get_item(map, Z_LVAL_P(offset), rv);
} else if (Z_TYPE_P(offset) == IS_DOUBLE) {
php_dom_named_node_map_get_item_into_zval(map, zend_dval_to_lval_safe(Z_DVAL_P(offset)), rv);
map->handler->get_item(map, zend_dval_to_lval_safe(Z_DVAL_P(offset)), rv);
} else {
zend_illegal_container_offset(object->ce->name, offset, type);
return NULL;
@ -2453,7 +2457,7 @@ static int dom_modern_nodemap_has_dimension(zend_object *object, zval *member, i
if (ZEND_HANDLE_NUMERIC(Z_STR_P(member), lval)) {
return (zend_long) lval >= 0 && (zend_long) lval < php_dom_get_namednodemap_length(obj);
} else {
return php_dom_named_node_map_get_named_item(map, Z_STR_P(member), false) != NULL;
return map->handler->has_named_item(map, Z_STR_P(member), NULL);
}
} else if (Z_TYPE_P(member) == IS_LONG) {
zend_long offset = Z_LVAL_P(member);

View file

@ -56,13 +56,12 @@ extern zend_module_entry dom_module_entry;
#include "xpath_callbacks.h"
#include "zend_exceptions.h"
#include "dom_ce.h"
#include "obj_map.h"
/* DOM API_VERSION, please bump it up, if you change anything in the API
therefore it's easier for the script-programmers to check, what's working how
Can be checked with phpversion("dom");
*/
#define DOM_API_VERSION "20031129"
/* Define a custom type for iterating using an unused nodetype */
#define DOM_NODESET XML_XINCLUDE_START
typedef struct dom_xpath_object {
php_dom_xpath_callbacks xpath_callbacks;
@ -80,7 +79,6 @@ static inline dom_xpath_object *php_xpath_obj_from_obj(zend_object *obj) {
typedef struct dom_nnodemap_object {
dom_object *baseobj;
zval baseobj_zv;
int nodetype;
int cached_length;
xmlHashTable *ht;
xmlChar *local;
@ -90,6 +88,7 @@ typedef struct dom_nnodemap_object {
dom_object *cached_obj;
zend_long cached_obj_index;
xmlDictPtr dict;
const php_dom_obj_map_handler *handler;
bool release_local : 1;
bool release_ns : 1;
} dom_nnodemap_object;
@ -146,9 +145,9 @@ int dom_hierarchy(xmlNodePtr parent, xmlNodePtr child);
bool dom_has_feature(zend_string *feature, zend_string *version);
bool dom_node_is_read_only(const xmlNode *node);
bool dom_node_children_valid(const xmlNode *node);
void dom_namednode_iter(dom_object *basenode, int ntype, dom_object *intern, xmlHashTablePtr ht, zend_string *local, zend_string *ns);
void dom_namednode_iter(dom_object *basenode, dom_object *intern, xmlHashTablePtr ht, zend_string *local, zend_string *ns, const php_dom_obj_map_handler *handler);
xmlNodePtr create_notation(const xmlChar *name, const xmlChar *ExternalID, const xmlChar *SystemID);
xmlNode *php_dom_libxml_hash_iter(dom_nnodemap_object *objmap, int index);
xmlNode *php_dom_libxml_hash_iter(xmlHashTable *ht, int index);
zend_object_iterator *php_dom_get_iterator(zend_class_entry *ce, zval *object, int by_ref);
void dom_set_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece, zend_class_entry *ce);
xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr original, zval *return_value, dom_object *parent_intern);
@ -211,10 +210,8 @@ void dom_element_closest(xmlNodePtr thisp, dom_object *intern, zval *return_valu
xmlNodePtr dom_parse_fragment(dom_object *obj, xmlNodePtr context_node, const zend_string *input);
/* nodemap and nodelist APIs */
xmlNodePtr php_dom_named_node_map_get_named_item(dom_nnodemap_object *objmap, const zend_string *named, bool may_transform);
void php_dom_named_node_map_get_named_item_into_zval(dom_nnodemap_object *objmap, const zend_string *named, zval *return_value);
void php_dom_named_node_map_get_named_item_into_zval(dom_nnodemap_object *objmap, const zend_string *named, const char *ns, 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);
zend_long php_dom_get_namednodemap_length(dom_object *obj);
xmlNodePtr dom_nodelist_iter_start_first_child(xmlNodePtr nodep);

View file

@ -36,8 +36,8 @@ foreach ($new->getElementsByTagName('child') as $child) {
echo $new->saveXml(), "\n";
?>
--EXPECT--
object(Dom\NamedNodeMap)#5 (1) {
--EXPECTF--
object(Dom\NamedNodeMap)#%d (1) {
["length"]=>
int(2)
}
@ -47,7 +47,7 @@ namespaceURI: string(29) "http://www.w3.org/2000/xmlns/"
name: string(1) "a"
prefix: NULL
namespaceURI: NULL
object(Dom\NamedNodeMap)#3 (1) {
object(Dom\NamedNodeMap)#%d (1) {
["length"]=>
int(3)
}

View file

@ -22,7 +22,7 @@ foreach ($test_values as $value) {
?>
--EXPECTF--
--- -1 ---
string(1) "a"
string(3) "N/A"
bool(false)
bool(true)
--- 0 ---

View file

@ -239,7 +239,7 @@ static void dom_xpath_iter(zval *baseobj, dom_object *intern) /* {{{ */
dom_nnodemap_object *mapptr = (dom_nnodemap_object *) intern->ptr;
ZVAL_COPY_VALUE(&mapptr->baseobj_zv, baseobj);
mapptr->nodetype = DOM_NODESET;
mapptr->handler = &php_dom_obj_map_nodeset;
}
/* }}} */