/* +----------------------------------------------------------------------+ | 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 | | Rob Richards | | Marcus Borger | +----------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H #include #endif #include "php.h" #if defined(HAVE_LIBXML) && defined(HAVE_DOM) #include "zend_enum.h" #include "php_dom.h" #include "nodelist.h" #include "html_collection.h" #include "namespace_compat.h" #include "private_data.h" #include "internal_helpers.h" #include "php_dom_arginfo.h" #include "dom_properties.h" #include "token_list.h" #include "zend_interfaces.h" #include "lexbor/lexbor/core/types.h" #include "lexbor/lexbor/core/lexbor.h" #include "ext/standard/info.h" /* {{{ class entries */ PHP_DOM_EXPORT zend_class_entry *dom_node_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_node_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_domexception_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_parentnode_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_parentnode_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_childnode_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_childnode_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_domimplementation_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_domimplementation_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_documentfragment_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_documentfragment_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_document_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_html_document_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_xml_document_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_nodelist_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_nodelist_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_namednodemap_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_namednodemap_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_dtd_namednodemap_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_html_collection_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_characterdata_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_characterdata_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_attr_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_attr_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_element_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_element_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_html_element_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_text_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_text_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_comment_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_comment_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_cdatasection_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_cdatasection_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_documenttype_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_documenttype_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_notation_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_notation_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_entity_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_entity_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_entityreference_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_entityreference_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_processinginstruction_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_processinginstruction_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_abstract_base_document_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_token_list_class_entry; #ifdef LIBXML_XPATH_ENABLED PHP_DOM_EXPORT zend_class_entry *dom_xpath_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_modern_xpath_class_entry; #endif PHP_DOM_EXPORT zend_class_entry *dom_namespace_node_class_entry; PHP_DOM_EXPORT zend_class_entry *dom_adjacent_position_class_entry; 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_modern_nnodemap_object_handlers; static zend_object_handlers dom_modern_nodelist_object_handlers; static zend_object_handlers dom_html_collection_object_handlers; static zend_object_handlers dom_object_namespace_node_handlers; static zend_object_handlers dom_modern_domimplementation_object_handlers; static zend_object_handlers dom_token_list_object_handlers; #ifdef LIBXML_XPATH_ENABLED zend_object_handlers dom_xpath_object_handlers; #endif static HashTable classes; /* {{{ prop handler tables */ static HashTable dom_document_prop_handlers; static HashTable dom_xml_document_prop_handlers; static HashTable dom_abstract_base_document_prop_handlers; static HashTable dom_documentfragment_prop_handlers; static HashTable dom_modern_documentfragment_prop_handlers; static HashTable dom_node_prop_handlers; static HashTable dom_modern_node_prop_handlers; static HashTable dom_entity_reference_prop_handlers; static HashTable dom_modern_entity_reference_prop_handlers; static HashTable dom_nodelist_prop_handlers; static HashTable dom_namednodemap_prop_handlers; static HashTable dom_characterdata_prop_handlers; static HashTable dom_modern_characterdata_prop_handlers; static HashTable dom_attr_prop_handlers; static HashTable dom_modern_attr_prop_handlers; static HashTable dom_element_prop_handlers; static HashTable dom_modern_element_prop_handlers; static HashTable dom_modern_element_prop_handlers; static HashTable dom_text_prop_handlers; static HashTable dom_modern_text_prop_handlers; static HashTable dom_documenttype_prop_handlers; static HashTable dom_modern_documenttype_prop_handlers; static HashTable dom_notation_prop_handlers; static HashTable dom_modern_notation_prop_handlers; static HashTable dom_entity_prop_handlers; static HashTable dom_modern_entity_prop_handlers; static HashTable dom_processinginstruction_prop_handlers; static HashTable dom_modern_processinginstruction_prop_handlers; static HashTable dom_namespace_node_prop_handlers; static HashTable dom_token_list_prop_handlers; #ifdef LIBXML_XPATH_ENABLED static HashTable dom_xpath_prop_handlers; #endif /* }}} */ static zend_object *dom_objects_namespace_node_new(zend_class_entry *class_type); static void dom_object_namespace_node_free_storage(zend_object *object); static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original); typedef zend_result (*dom_read_t)(dom_object *obj, zval *retval); typedef zend_result (*dom_write_t)(dom_object *obj, zval *newval); typedef struct dom_prop_handler { dom_read_t read_func; dom_write_t write_func; } dom_prop_handler; int dom_node_is_read_only(const xmlNode *node) { switch (node->type) { case XML_ENTITY_REF_NODE: case XML_ENTITY_NODE: case XML_DOCUMENT_TYPE_NODE: case XML_NOTATION_NODE: case XML_DTD_NODE: case XML_ELEMENT_DECL: case XML_ATTRIBUTE_DECL: case XML_ENTITY_DECL: case XML_NAMESPACE_DECL: return SUCCESS; break; default: if (node->doc == NULL) { return SUCCESS; } else { return FAILURE; } } } bool dom_node_children_valid(const xmlNode *node) { switch (node->type) { case XML_DOCUMENT_TYPE_NODE: case XML_DTD_NODE: case XML_PI_NODE: case XML_COMMENT_NODE: case XML_TEXT_NODE: case XML_CDATA_SECTION_NODE: case XML_NOTATION_NODE: return false; default: return true; } } static const libxml_doc_props default_doc_props = { .formatoutput = false, .validateonparse = false, .resolveexternals = false, .preservewhitespace = true, .substituteentities = false, .stricterror = true, .recover = false, .classmap = NULL, }; ZEND_DECLARE_MODULE_GLOBALS(dom) static PHP_GINIT_FUNCTION(dom) { #if defined(COMPILE_DL_DOM) && defined(ZTS) ZEND_TSRMLS_CACHE_UPDATE(); #endif dom_globals->suppress_warnings = false; } /* {{{ dom_get_doc_props() */ dom_doc_propsptr dom_get_doc_props(php_libxml_ref_obj *document) { dom_doc_propsptr doc_props; if (document && document->doc_props) { return document->doc_props; } else { doc_props = emalloc(sizeof(libxml_doc_props)); memcpy(doc_props, &default_doc_props, sizeof(libxml_doc_props)); if (document) { document->doc_props = doc_props; } return doc_props; } } /* }}} */ libxml_doc_props const* dom_get_doc_props_read_only(const php_libxml_ref_obj *document) { if (document && document->doc_props) { return document->doc_props; } else { return &default_doc_props; } } static void dom_copy_document_ref(php_libxml_ref_obj *source_doc, php_libxml_ref_obj *dest_doc) { dom_doc_propsptr dest; if (source_doc && dest_doc) { libxml_doc_props const* source = dom_get_doc_props_read_only(source_doc); dest = dom_get_doc_props(dest_doc); dest->formatoutput = source->formatoutput; dest->validateonparse = source->validateonparse; dest->resolveexternals = source->resolveexternals; dest->preservewhitespace = source->preservewhitespace; dest->substituteentities = source->substituteentities; dest->stricterror = source->stricterror; dest->recover = source->recover; if (source->classmap) { ALLOC_HASHTABLE(dest->classmap); zend_hash_init(dest->classmap, 0, NULL, NULL, false); zend_hash_copy(dest->classmap, source->classmap, NULL); } dest_doc->class_type = source_doc->class_type; dest_doc->handlers = source_doc->handlers; } } void dom_set_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece, zend_class_entry *ce) { dom_doc_propsptr doc_props; if (document) { doc_props = dom_get_doc_props(document); if (doc_props->classmap == NULL) { if (ce == NULL) { return; } ALLOC_HASHTABLE(doc_props->classmap); zend_hash_init(doc_props->classmap, 0, NULL, NULL, false); } if (ce) { zend_hash_update_ptr(doc_props->classmap, basece->name, ce); } else { zend_hash_del(doc_props->classmap, basece->name); } } } zend_class_entry *dom_get_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece) { if (document) { libxml_doc_props const* doc_props = dom_get_doc_props_read_only(document); if (doc_props->classmap) { zend_class_entry *ce = zend_hash_find_ptr(doc_props->classmap, basece->name); if (ce) { return ce; } } } return basece; } /* }}} */ /* {{{ dom_get_strict_error() */ bool dom_get_strict_error(php_libxml_ref_obj *document) { return dom_get_doc_props_read_only(document)->stricterror; } /* }}} */ /* {{{ xmlNodePtr dom_object_get_node(dom_object *obj) */ PHP_DOM_EXPORT xmlNodePtr dom_object_get_node(dom_object *obj) { if (obj && obj->ptr != NULL) { return ((php_libxml_node_ptr *)obj->ptr)->node; } else { return NULL; } } /* }}} end dom_object_get_node */ /* {{{ dom_object *php_dom_object_get_data(xmlNodePtr obj) */ PHP_DOM_EXPORT dom_object *php_dom_object_get_data(xmlNodePtr obj) { if (obj && obj->_private != NULL) { return (dom_object *) ((php_libxml_node_ptr *) obj->_private)->_private; } else { return NULL; } } /* }}} end php_dom_object_get_data */ static void dom_register_prop_handler(HashTable *prop_handler, const char *name, size_t name_len, const dom_prop_handler *hnd) { zend_string *str = zend_string_init_interned(name, name_len, true); zend_hash_add_new_ptr(prop_handler, str, (void *) hnd); zend_string_release_ex(str, true); } static void dom_overwrite_prop_handler(HashTable *prop_handler, const char *name, size_t name_len, const dom_prop_handler *hnd) { zend_hash_str_update_ptr(prop_handler, name, name_len, (void *) hnd); } #define DOM_REGISTER_PROP_HANDLER(prop_handler, name, prop_read_func, prop_write_func) do { \ static const dom_prop_handler hnd = {.read_func = prop_read_func, .write_func = prop_write_func}; \ dom_register_prop_handler(prop_handler, "" name, sizeof("" name) - 1, &hnd); \ } while (0) #define DOM_OVERWRITE_PROP_HANDLER(prop_handler, name, prop_read_func, prop_write_func) do { \ static const dom_prop_handler hnd = {.read_func = prop_read_func, .write_func = prop_write_func}; \ dom_overwrite_prop_handler(prop_handler, "" name, sizeof("" name) - 1, &hnd); \ } while (0) static zval *dom_get_property_ptr_ptr(zend_object *object, zend_string *name, int type, void **cache_slot) { dom_object *obj = php_dom_obj_from_obj(object); if (!obj->prop_handler || !zend_hash_exists(obj->prop_handler, name)) { return zend_std_get_property_ptr_ptr(object, name, type, cache_slot); } return NULL; } static zend_always_inline const dom_prop_handler *dom_get_prop_handler(const dom_object *obj, zend_string *name, void **cache_slot) { const dom_prop_handler *hnd = NULL; if (obj->prop_handler != NULL) { if (cache_slot && *cache_slot == obj->prop_handler) { hnd = *(cache_slot + 1); } if (!hnd) { hnd = zend_hash_find_ptr(obj->prop_handler, name); if (cache_slot) { *cache_slot = obj->prop_handler; *(cache_slot + 1) = (void *) hnd; } } } return hnd; } /* {{{ dom_read_property */ zval *dom_read_property(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv) { dom_object *obj = php_dom_obj_from_obj(object); zval *retval; const dom_prop_handler *hnd = dom_get_prop_handler(obj, name, cache_slot); if (hnd) { int ret = hnd->read_func(obj, rv); if (ret == SUCCESS) { retval = rv; } else { retval = &EG(uninitialized_zval); } } else { retval = zend_std_read_property(object, name, type, cache_slot, rv); } return retval; } /* }}} */ zval *dom_write_property(zend_object *object, zend_string *name, zval *value, void **cache_slot) { dom_object *obj = php_dom_obj_from_obj(object); const dom_prop_handler *hnd = dom_get_prop_handler(obj, name, cache_slot); if (hnd) { if (UNEXPECTED(!hnd->write_func)) { zend_readonly_property_modification_error_ex(ZSTR_VAL(object->ce->name), ZSTR_VAL(name)); return &EG(error_zval); } zend_property_info *prop = NULL; if (cache_slot) { ZEND_ASSERT(*cache_slot == obj->prop_handler); prop = *(cache_slot + 2); } if (!prop) { prop = zend_get_property_info(object->ce, name, /* silent */ true); if (cache_slot) { *(cache_slot + 2) = prop; } } ZEND_ASSERT(prop && ZEND_TYPE_IS_SET(prop->type)); zval tmp; ZVAL_COPY(&tmp, value); if (!zend_verify_property_type(prop, &tmp, ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data)))) { zval_ptr_dtor(&tmp); return &EG(error_zval); } hnd->write_func(obj, &tmp); zval_ptr_dtor(&tmp); return value; } return zend_std_write_property(object, name, value, cache_slot); } // todo: make dom_property_exists return bool as well /* {{{ dom_property_exists */ static int dom_property_exists(zend_object *object, zend_string *name, int check_empty, void **cache_slot) { dom_object *obj = php_dom_obj_from_obj(object); dom_prop_handler *hnd = NULL; bool retval = false; if (obj->prop_handler != NULL) { hnd = zend_hash_find_ptr(obj->prop_handler, name); } if (hnd) { zval tmp; if (check_empty == 2) { retval = true; } else if (hnd->read_func(obj, &tmp) == SUCCESS) { if (check_empty == 1) { retval = zend_is_true(&tmp); } else if (check_empty == 0) { retval = (Z_TYPE(tmp) != IS_NULL); } zval_ptr_dtor(&tmp); } } else { retval = zend_std_has_property(object, name, check_empty, cache_slot); } return retval; } /* }}} */ static HashTable* dom_get_debug_info_helper(zend_object *object, int *is_temp) /* {{{ */ { dom_object *obj = php_dom_obj_from_obj(object); HashTable *debug_info, *prop_handlers = obj->prop_handler, *std_props; zend_string *string_key; dom_prop_handler *entry; zend_string *object_str; *is_temp = 1; std_props = zend_std_get_properties(object); debug_info = zend_array_dup(std_props); if (!prop_handlers) { return debug_info; } DOM_G(suppress_warnings) = true; object_str = ZSTR_INIT_LITERAL("(object value omitted)", false); ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(prop_handlers, string_key, entry) { zval value; ZEND_ASSERT(string_key != NULL); if (entry->read_func(obj, &value) == FAILURE) { continue; } if (Z_TYPE(value) == IS_OBJECT) { zval_ptr_dtor(&value); ZVAL_NEW_STR(&value, object_str); zend_string_addref(object_str); } zend_hash_update(debug_info, string_key, &value); } ZEND_HASH_FOREACH_END(); zend_string_release_ex(object_str, false); DOM_G(suppress_warnings) = false; return debug_info; } /* }}} */ static HashTable* dom_get_debug_info(zend_object *object, int *is_temp) /* {{{ */ { return dom_get_debug_info_helper(object, is_temp); } /* }}} */ void *php_dom_export_node(zval *object) /* {{{ */ { php_libxml_node_object *intern; xmlNodePtr nodep = NULL; intern = (php_libxml_node_object *) Z_DOMOBJ_P(object); if (intern->node) { nodep = intern->node->node; } return nodep; } /* }}} */ /* {{{ Get a simplexml_element object from dom to allow for processing */ static void dom_import_simplexml_common(INTERNAL_FUNCTION_PARAMETERS, php_libxml_class_type new_class) { zval *node; xmlNodePtr nodep = NULL; php_libxml_node_object *nodeobj; if (zend_parse_parameters(ZEND_NUM_ARGS(), "o", &node) == FAILURE) { RETURN_THROWS(); } nodeobj = (php_libxml_node_object *) ((char *) Z_OBJ_P(node) - Z_OBJ_HT_P(node)->offset); nodep = php_libxml_import_node(node); if (nodep && nodeobj && nodeobj->document && (nodep->type == XML_ELEMENT_NODE || nodep->type == XML_ATTRIBUTE_NODE)) { php_libxml_class_type old_class_type = nodeobj->document->class_type; if (old_class_type != PHP_LIBXML_CLASS_UNSET && old_class_type != new_class) { if (new_class == PHP_LIBXML_CLASS_MODERN) { zend_argument_type_error(1, "must not be already imported as a DOMNode"); } else { zend_argument_type_error(1, "must not be already imported as a Dom\\Node"); } RETURN_THROWS(); } /* Lock the node class type to prevent creating multiple representations of the same node. */ nodeobj->document->class_type = new_class; if (old_class_type != PHP_LIBXML_CLASS_MODERN && new_class == PHP_LIBXML_CLASS_MODERN && nodep->doc != NULL) { dom_document_convert_to_modern(nodeobj->document, nodep->doc); } DOM_RET_OBJ((xmlNodePtr) nodep, (dom_object *)nodeobj); } else { zend_argument_type_error(1, "is not a valid node type"); RETURN_THROWS(); } } PHP_FUNCTION(dom_import_simplexml) { dom_import_simplexml_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_LIBXML_CLASS_LEGACY); } PHP_FUNCTION(Dom_import_simplexml) { dom_import_simplexml_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_LIBXML_CLASS_MODERN); } /* }}} */ static dom_object* dom_objects_set_class(zend_class_entry *class_type); void php_dom_update_document_after_clone(dom_object *original, xmlNodePtr original_node, dom_object *clone, xmlNodePtr cloned_node) { dom_copy_document_ref(original->document, clone->document); /* Workaround libxml2 bug, see https://gitlab.gnome.org/GNOME/libxml2/-/commit/07920b4381873187c02df53fa9b5d44aff3a7041 */ #if LIBXML_VERSION < 20911 if (original_node->type == XML_HTML_DOCUMENT_NODE) { cloned_node->type = XML_HTML_DOCUMENT_NODE; } #endif } static void dom_update_refcount_after_clone(dom_object *original, xmlNodePtr original_node, dom_object *clone, xmlNodePtr cloned_node) { /* If we cloned a document then we must create new doc proxy */ if (cloned_node->doc == original_node->doc) { clone->document = original->document; } php_libxml_increment_doc_ref((php_libxml_node_object *)clone, cloned_node->doc); php_libxml_increment_node_ptr((php_libxml_node_object *)clone, cloned_node, (void *)clone); if (original->document != clone->document) { php_dom_update_document_after_clone(original, original_node, clone, cloned_node); } } static zend_object *dom_objects_store_clone_obj(zend_object *zobject) /* {{{ */ { dom_object *intern = php_dom_obj_from_obj(zobject); dom_object *clone = dom_objects_set_class(intern->std.ce); if (instanceof_function(intern->std.ce, dom_node_class_entry) || instanceof_function(intern->std.ce, dom_modern_node_class_entry)) { xmlNodePtr node = (xmlNodePtr)dom_object_get_node(intern); if (node != NULL) { php_dom_private_data *private_data = NULL; if (php_dom_follow_spec_intern(intern)) { if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) { private_data = php_dom_private_data_create(); } else { private_data = php_dom_get_private_data(intern); } } xmlNodePtr cloned_node = dom_clone_node(php_dom_ns_mapper_from_private(private_data), node, node->doc, true); if (cloned_node != NULL) { dom_update_refcount_after_clone(intern, node, clone, cloned_node); } if (private_data != NULL) { clone->document->private_data = php_dom_libxml_private_data_header(private_data); } } } zend_objects_clone_members(&clone->std, &intern->std); return &clone->std; } /* }}} */ static zend_object *dom_object_namespace_node_clone_obj(zend_object *zobject) { dom_object_namespace_node *intern = php_dom_namespace_node_obj_from_obj(zobject); zend_object *clone = dom_objects_namespace_node_new(intern->dom.std.ce); dom_object_namespace_node *clone_intern = php_dom_namespace_node_obj_from_obj(clone); xmlNodePtr original_node = dom_object_get_node(&intern->dom); ZEND_ASSERT(original_node->type == XML_NAMESPACE_DECL); xmlNodePtr cloned_node = php_dom_create_fake_namespace_decl_node_ptr(original_node->parent, original_node->ns); if (intern->parent_intern) { clone_intern->parent_intern = intern->parent_intern; GC_ADDREF(&clone_intern->parent_intern->std); } dom_update_refcount_after_clone(&intern->dom, original_node, &clone_intern->dom, cloned_node); zend_objects_clone_members(clone, &intern->dom.std); return clone; } static zend_object *dom_token_list_new(zend_class_entry *class_type) { dom_token_list_object *intern = zend_object_alloc(sizeof(*intern), class_type); intern->dom.prop_handler = &dom_token_list_prop_handlers; zend_object_std_init(&intern->dom.std, class_type); object_properties_init(&intern->dom.std, class_type); return &intern->dom.std; } static const zend_module_dep dom_deps[] = { ZEND_MOD_REQUIRED("libxml") ZEND_MOD_CONFLICTS("domxml") ZEND_MOD_END }; zend_module_entry dom_module_entry = { /* {{{ */ STANDARD_MODULE_HEADER_EX, NULL, dom_deps, "dom", ext_functions, PHP_MINIT(dom), PHP_MSHUTDOWN(dom), NULL, NULL, PHP_MINFO(dom), DOM_API_VERSION, /* Extension versionnumber */ PHP_MODULE_GLOBALS(dom), PHP_GINIT(dom), NULL, NULL, STANDARD_MODULE_PROPERTIES_EX }; /* }}} */ #ifdef COMPILE_DL_DOM ZEND_GET_MODULE(dom) #endif void dom_nnodemap_objects_free_storage(zend_object *object); static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv); static int dom_nodelist_has_dimension(zend_object *object, zval *member, int check_empty); static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv); static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty); static zval *dom_modern_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv); static int dom_modern_nodemap_has_dimension(zend_object *object, zval *member, int check_empty); #ifdef LIBXML_XPATH_ENABLED void dom_xpath_objects_free_storage(zend_object *object); HashTable *dom_xpath_get_gc(zend_object *object, zval **table, int *n); #endif static void *dom_malloc(size_t size) { return emalloc(size); } static void *dom_realloc(void *dst, size_t size) { return erealloc(dst, size); } static void *dom_calloc(size_t num, size_t size) { return ecalloc(num, size); } static void dom_free(void *ptr) { efree(ptr); } /* {{{ PHP_MINIT_FUNCTION(dom) */ PHP_MINIT_FUNCTION(dom) { memcpy(&dom_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); dom_object_handlers.offset = XtOffsetOf(dom_object, std); dom_object_handlers.free_obj = dom_objects_free_storage; dom_object_handlers.read_property = dom_read_property; dom_object_handlers.write_property = dom_write_property; dom_object_handlers.get_property_ptr_ptr = dom_get_property_ptr_ptr; dom_object_handlers.clone_obj = dom_objects_store_clone_obj; dom_object_handlers.has_property = dom_property_exists; dom_object_handlers.get_debug_info = dom_get_debug_info; memcpy(&dom_modern_domimplementation_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers)); /* The IDL has the [SameObject] constraint, which is incompatible with cloning because it imposes that there is only * one instance per parent object. */ dom_modern_domimplementation_object_handlers.clone_obj = NULL; 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; dom_nnodemap_object_handlers.has_dimension = dom_nodemap_has_dimension; memcpy(&dom_nodelist_object_handlers, &dom_nnodemap_object_handlers, sizeof(zend_object_handlers)); dom_nodelist_object_handlers.read_dimension = dom_nodelist_read_dimension; dom_nodelist_object_handlers.has_dimension = dom_nodelist_has_dimension; memcpy(&dom_modern_nnodemap_object_handlers, &dom_nnodemap_object_handlers, sizeof(zend_object_handlers)); dom_modern_nnodemap_object_handlers.read_dimension = dom_modern_nodemap_read_dimension; dom_modern_nnodemap_object_handlers.has_dimension = dom_modern_nodemap_has_dimension; memcpy(&dom_modern_nodelist_object_handlers, &dom_nodelist_object_handlers, sizeof(zend_object_handlers)); dom_modern_nodelist_object_handlers.read_dimension = dom_modern_nodelist_read_dimension; dom_modern_nodelist_object_handlers.has_dimension = dom_modern_nodelist_has_dimension; 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; 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); dom_object_namespace_node_handlers.free_obj = dom_object_namespace_node_free_storage; dom_object_namespace_node_handlers.clone_obj = dom_object_namespace_node_clone_obj; memcpy(&dom_token_list_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers)); dom_token_list_object_handlers.offset = XtOffsetOf(dom_token_list_object, dom.std); dom_token_list_object_handlers.free_obj = dom_token_list_free_obj; /* The Web IDL (Web Interface Description Language - https://webidl.spec.whatwg.org) has the [SameObject] constraint * for this object, which is incompatible with cloning because it imposes that there is only one instance * per parent object. */ dom_token_list_object_handlers.clone_obj = NULL; dom_token_list_object_handlers.read_dimension = dom_token_list_read_dimension; dom_token_list_object_handlers.has_dimension = dom_token_list_has_dimension; zend_hash_init(&classes, 0, NULL, NULL, true); dom_adjacent_position_class_entry = register_class_Dom_AdjacentPosition(); dom_domexception_class_entry = register_class_DOMException(zend_ce_exception); dom_parentnode_class_entry = register_class_DOMParentNode(); dom_modern_parentnode_class_entry = register_class_Dom_ParentNode(); dom_childnode_class_entry = register_class_DOMChildNode(); dom_modern_childnode_class_entry = register_class_Dom_ChildNode(); dom_domimplementation_class_entry = register_class_DOMImplementation(); dom_domimplementation_class_entry->create_object = dom_objects_new; dom_domimplementation_class_entry->default_object_handlers = &dom_object_handlers; dom_modern_domimplementation_class_entry = register_class_Dom_Implementation(); dom_modern_domimplementation_class_entry->create_object = dom_objects_new; dom_modern_domimplementation_class_entry->default_object_handlers = &dom_modern_domimplementation_object_handlers; dom_node_class_entry = register_class_DOMNode(); dom_node_class_entry->create_object = dom_objects_new; dom_node_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_node_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "nodeName", dom_node_node_name_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "nodeValue", dom_node_node_value_read, dom_node_node_value_write); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "nodeType", dom_node_node_type_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "parentNode", dom_node_parent_node_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "parentElement", dom_node_parent_element_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "childNodes", dom_node_child_nodes_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "firstChild", dom_node_first_child_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "lastChild", dom_node_last_child_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "previousSibling", dom_node_previous_sibling_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "nextSibling", dom_node_next_sibling_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "attributes", dom_node_attributes_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "isConnected", dom_node_is_connected_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "ownerDocument", dom_node_owner_document_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "namespaceURI", dom_node_namespace_uri_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "prefix", dom_node_prefix_read, dom_node_prefix_write); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "localName", dom_node_local_name_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "baseURI", dom_node_base_uri_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_node_prop_handlers, "textContent", dom_node_text_content_read, dom_node_text_content_write); zend_hash_add_new_ptr(&classes, dom_node_class_entry->name, &dom_node_prop_handlers); dom_modern_node_class_entry = register_class_Dom_Node(); dom_modern_node_class_entry->create_object = dom_objects_new; dom_modern_node_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_modern_node_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "nodeType", dom_node_node_type_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "nodeName", dom_node_node_name_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "baseURI", dom_node_base_uri_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "isConnected", dom_node_is_connected_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "ownerDocument", dom_node_owner_document_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "parentNode", dom_node_parent_node_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "parentElement", dom_node_parent_element_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "childNodes", dom_node_child_nodes_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "firstChild", dom_node_first_child_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "lastChild", dom_node_last_child_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "previousSibling", dom_node_previous_sibling_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "nextSibling", dom_node_next_sibling_read, NULL); /* We will set-up the setter for the derived classes afterwards on a class-by-class basis. */ DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "nodeValue", dom_node_node_value_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "textContent", dom_node_text_content_read, NULL); zend_hash_add_new_ptr(&classes, dom_modern_node_class_entry->name, &dom_modern_node_prop_handlers); dom_namespace_node_class_entry = register_class_DOMNameSpaceNode(); dom_namespace_node_class_entry->create_object = dom_objects_namespace_node_new; dom_namespace_node_class_entry->default_object_handlers = &dom_object_namespace_node_handlers; zend_hash_init(&dom_namespace_node_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "nodeName", dom_node_node_name_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "nodeValue", dom_node_node_value_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "nodeType", dom_node_node_type_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "prefix", dom_node_prefix_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "localName", dom_node_local_name_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "namespaceURI", dom_node_namespace_uri_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "isConnected", dom_node_is_connected_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "ownerDocument", dom_node_owner_document_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "parentNode", dom_node_parent_node_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "parentElement", dom_node_parent_element_read, NULL); zend_hash_add_new_ptr(&classes, dom_namespace_node_class_entry->name, &dom_namespace_node_prop_handlers); dom_namespace_info_class_entry = register_class_Dom_NamespaceInfo(); dom_documentfragment_class_entry = register_class_DOMDocumentFragment(dom_node_class_entry, dom_parentnode_class_entry); dom_documentfragment_class_entry->create_object = dom_objects_new; dom_documentfragment_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_documentfragment_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_documentfragment_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_documentfragment_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_documentfragment_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL); zend_hash_merge(&dom_documentfragment_prop_handlers, &dom_node_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_documentfragment_class_entry->name, &dom_documentfragment_prop_handlers); 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; zend_hash_init(&dom_modern_documentfragment_prop_handlers, 0, NULL, NULL, true); 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); zend_hash_merge(&dom_modern_documentfragment_prop_handlers, &dom_modern_node_prop_handlers, NULL, false); DOM_OVERWRITE_PROP_HANDLER(&dom_modern_documentfragment_prop_handlers, "textContent", dom_node_text_content_read, dom_node_text_content_write); 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; 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); DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "documentURI", dom_document_document_uri_read, dom_document_document_uri_write); DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "characterSet", dom_document_encoding_read, dom_html_document_encoding_write); DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "charset", dom_document_encoding_read, dom_html_document_encoding_write); 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, "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); DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "body", dom_html_document_body_read, dom_html_document_body_write); DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "head", dom_html_document_head_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "title", dom_html_document_title_read, dom_html_document_title_write); zend_hash_merge(&dom_abstract_base_document_prop_handlers, &dom_modern_node_prop_handlers, NULL, false); /* No need to register in &classes because this is an abstract class handler. */ dom_document_class_entry = register_class_DOMDocument(dom_node_class_entry, dom_parentnode_class_entry); dom_document_class_entry->create_object = dom_objects_new; dom_document_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_document_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "doctype", dom_document_doctype_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "implementation", dom_document_implementation_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "documentElement", dom_document_document_element_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "actualEncoding", dom_document_actual_encoding_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "encoding", dom_document_encoding_read, dom_document_encoding_write); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "xmlEncoding", dom_document_encoding_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "standalone", dom_document_standalone_read, dom_document_standalone_write); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "xmlStandalone", dom_document_standalone_read, dom_document_standalone_write); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "version", dom_document_version_read, dom_document_version_write); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "xmlVersion", dom_document_version_read, dom_document_version_write); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "strictErrorChecking", dom_document_strict_error_checking_read, dom_document_strict_error_checking_write); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "documentURI", dom_document_document_uri_read, dom_document_document_uri_write); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "config", dom_document_config_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "formatOutput", dom_document_format_output_read, dom_document_format_output_write); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "validateOnParse", dom_document_validate_on_parse_read, dom_document_validate_on_parse_write); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "resolveExternals", dom_document_resolve_externals_read, dom_document_resolve_externals_write); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "preserveWhiteSpace", dom_document_preserve_whitespace_read, dom_document_preserve_whitespace_write); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "recover", dom_document_recover_read, dom_document_recover_write); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "substituteEntities", dom_document_substitue_entities_read, dom_document_substitue_entities_write); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_document_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL); zend_hash_merge(&dom_document_prop_handlers, &dom_node_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_document_class_entry->name, &dom_document_prop_handlers); dom_html_document_class_entry = register_class_Dom_HTMLDocument(dom_abstract_base_document_class_entry); dom_html_document_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_add_new_ptr(&classes, dom_html_document_class_entry->name, &dom_abstract_base_document_prop_handlers); dom_xml_document_class_entry = register_class_Dom_XMLDocument(dom_abstract_base_document_class_entry); dom_xml_document_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_xml_document_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_xml_document_prop_handlers, "xmlEncoding", dom_document_encoding_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_xml_document_prop_handlers, "xmlStandalone", dom_document_standalone_read, dom_document_standalone_write); DOM_REGISTER_PROP_HANDLER(&dom_xml_document_prop_handlers, "xmlVersion", dom_document_version_read, dom_document_version_write); DOM_REGISTER_PROP_HANDLER(&dom_xml_document_prop_handlers, "formatOutput", dom_document_format_output_read, dom_document_format_output_write); zend_hash_merge(&dom_xml_document_prop_handlers, &dom_abstract_base_document_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_xml_document_class_entry->name, &dom_xml_document_prop_handlers); dom_nodelist_class_entry = register_class_DOMNodeList(zend_ce_aggregate, zend_ce_countable); dom_nodelist_class_entry->create_object = dom_nnodemap_objects_new; dom_nodelist_class_entry->default_object_handlers = &dom_nodelist_object_handlers; dom_nodelist_class_entry->get_iterator = php_dom_get_iterator; zend_hash_init(&dom_nodelist_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_nodelist_prop_handlers, "length", dom_nodelist_length_read, NULL); zend_hash_add_new_ptr(&classes, dom_nodelist_class_entry->name, &dom_nodelist_prop_handlers); dom_modern_nodelist_class_entry = register_class_Dom_NodeList(zend_ce_aggregate, zend_ce_countable); dom_modern_nodelist_class_entry->create_object = dom_nnodemap_objects_new; dom_modern_nodelist_class_entry->default_object_handlers = &dom_modern_nodelist_object_handlers; dom_modern_nodelist_class_entry->get_iterator = php_dom_get_iterator; zend_hash_add_new_ptr(&classes, dom_modern_nodelist_class_entry->name, &dom_nodelist_prop_handlers); dom_namednodemap_class_entry = register_class_DOMNamedNodeMap(zend_ce_aggregate, zend_ce_countable); dom_namednodemap_class_entry->create_object = dom_nnodemap_objects_new; dom_namednodemap_class_entry->default_object_handlers = &dom_nnodemap_object_handlers; dom_namednodemap_class_entry->get_iterator = php_dom_get_iterator; zend_hash_init(&dom_namednodemap_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_namednodemap_prop_handlers, "length", dom_namednodemap_length_read, NULL); zend_hash_add_new_ptr(&classes, dom_namednodemap_class_entry->name, &dom_namednodemap_prop_handlers); dom_modern_namednodemap_class_entry = register_class_Dom_NamedNodeMap(zend_ce_aggregate, zend_ce_countable); dom_modern_namednodemap_class_entry->create_object = dom_nnodemap_objects_new; dom_modern_namednodemap_class_entry->default_object_handlers = &dom_modern_nnodemap_object_handlers; dom_modern_namednodemap_class_entry->get_iterator = php_dom_get_iterator; zend_hash_add_new_ptr(&classes, dom_modern_namednodemap_class_entry->name, &dom_namednodemap_prop_handlers); dom_modern_dtd_namednodemap_class_entry = register_class_Dom_DtdNamedNodeMap(zend_ce_aggregate, zend_ce_countable); dom_modern_dtd_namednodemap_class_entry->create_object = dom_nnodemap_objects_new; dom_modern_dtd_namednodemap_class_entry->default_object_handlers = &dom_modern_nnodemap_object_handlers; dom_modern_dtd_namednodemap_class_entry->get_iterator = php_dom_get_iterator; zend_hash_add_new_ptr(&classes, dom_modern_dtd_namednodemap_class_entry->name, &dom_namednodemap_prop_handlers); dom_html_collection_class_entry = register_class_Dom_HTMLCollection(zend_ce_aggregate, zend_ce_countable); dom_html_collection_class_entry->create_object = dom_nnodemap_objects_new; dom_html_collection_class_entry->default_object_handlers = &dom_html_collection_object_handlers; dom_html_collection_class_entry->get_iterator = php_dom_get_iterator; zend_hash_add_new_ptr(&classes, dom_html_collection_class_entry->name, &dom_nodelist_prop_handlers); dom_characterdata_class_entry = register_class_DOMCharacterData(dom_node_class_entry, dom_childnode_class_entry); dom_characterdata_class_entry->create_object = dom_objects_new; dom_characterdata_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_characterdata_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_characterdata_prop_handlers, "data", dom_characterdata_data_read, dom_characterdata_data_write); DOM_REGISTER_PROP_HANDLER(&dom_characterdata_prop_handlers, "length", dom_characterdata_length_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_characterdata_prop_handlers, "previousElementSibling", dom_node_previous_element_sibling_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_characterdata_prop_handlers, "nextElementSibling", dom_node_next_element_sibling_read, NULL); zend_hash_merge(&dom_characterdata_prop_handlers, &dom_node_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_characterdata_class_entry->name, &dom_characterdata_prop_handlers); dom_modern_characterdata_class_entry = register_class_Dom_CharacterData(dom_modern_node_class_entry, dom_modern_childnode_class_entry); dom_modern_characterdata_class_entry->create_object = dom_objects_new; dom_modern_characterdata_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_modern_characterdata_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_modern_characterdata_prop_handlers, "data", dom_characterdata_data_read, dom_characterdata_data_write); DOM_REGISTER_PROP_HANDLER(&dom_modern_characterdata_prop_handlers, "length", dom_characterdata_length_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_characterdata_prop_handlers, "previousElementSibling", dom_node_previous_element_sibling_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_characterdata_prop_handlers, "nextElementSibling", dom_node_next_element_sibling_read, NULL); zend_hash_merge(&dom_modern_characterdata_prop_handlers, &dom_modern_node_prop_handlers, NULL, false); DOM_OVERWRITE_PROP_HANDLER(&dom_modern_characterdata_prop_handlers, "nodeValue", dom_node_node_value_read, dom_node_node_value_write); DOM_OVERWRITE_PROP_HANDLER(&dom_modern_characterdata_prop_handlers, "textContent", dom_node_text_content_read, dom_node_text_content_write); zend_hash_add_new_ptr(&classes, dom_modern_characterdata_class_entry->name, &dom_modern_characterdata_prop_handlers); dom_attr_class_entry = register_class_DOMAttr(dom_node_class_entry); dom_attr_class_entry->create_object = dom_objects_new; dom_attr_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_attr_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "name", dom_attr_name_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "specified", dom_attr_specified_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "value", dom_attr_value_read, dom_attr_value_write); DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "ownerElement", dom_attr_owner_element_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_attr_prop_handlers, "schemaTypeInfo", dom_attr_schema_type_info_read, NULL); zend_hash_merge(&dom_attr_prop_handlers, &dom_node_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_attr_class_entry->name, &dom_attr_prop_handlers); dom_modern_attr_class_entry = register_class_Dom_Attr(dom_modern_node_class_entry); dom_modern_attr_class_entry->create_object = dom_objects_new; dom_modern_attr_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_modern_attr_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_modern_attr_prop_handlers, "namespaceURI", dom_node_namespace_uri_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_attr_prop_handlers, "prefix", dom_modern_node_prefix_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_attr_prop_handlers, "localName", dom_node_local_name_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_attr_prop_handlers, "name", dom_attr_name_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_attr_prop_handlers, "value", dom_attr_value_read, dom_attr_value_write); DOM_REGISTER_PROP_HANDLER(&dom_modern_attr_prop_handlers, "ownerElement", dom_attr_owner_element_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_attr_prop_handlers, "specified", dom_attr_specified_read, NULL); zend_hash_merge(&dom_modern_attr_prop_handlers, &dom_node_prop_handlers, NULL, false); DOM_OVERWRITE_PROP_HANDLER(&dom_modern_attr_prop_handlers, "nodeValue", dom_node_node_value_read, dom_node_node_value_write); DOM_OVERWRITE_PROP_HANDLER(&dom_modern_attr_prop_handlers, "textContent", dom_node_text_content_read, dom_node_text_content_write); zend_hash_add_new_ptr(&classes, dom_modern_attr_class_entry->name, &dom_modern_attr_prop_handlers); dom_element_class_entry = register_class_DOMElement(dom_node_class_entry, dom_parentnode_class_entry, dom_childnode_class_entry); dom_element_class_entry->create_object = dom_objects_new; dom_element_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_element_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "tagName", dom_element_tag_name_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "className", dom_element_class_name_read, dom_element_class_name_write); DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "id", dom_element_id_read, dom_element_id_write); DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "schemaTypeInfo", dom_element_schema_type_info_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL); DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "previousElementSibling", dom_node_previous_element_sibling_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_element_prop_handlers, "nextElementSibling", dom_node_next_element_sibling_read, NULL); zend_hash_merge(&dom_element_prop_handlers, &dom_node_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_element_class_entry->name, &dom_element_prop_handlers); dom_modern_element_class_entry = register_class_Dom_Element(dom_modern_node_class_entry, dom_modern_parentnode_class_entry, dom_modern_childnode_class_entry); dom_modern_element_class_entry->create_object = dom_objects_new; dom_modern_element_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_modern_element_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "namespaceURI", dom_node_namespace_uri_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "prefix", dom_modern_node_prefix_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "localName", dom_node_local_name_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "tagName", dom_element_tag_name_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "id", dom_element_id_read, dom_element_id_write); 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, "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); DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "previousElementSibling", dom_node_previous_element_sibling_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "nextElementSibling", dom_node_next_element_sibling_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "innerHTML", dom_element_inner_html_read, dom_element_inner_html_write); DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "substitutedNodeValue", dom_modern_element_substituted_node_value_read, dom_modern_element_substituted_node_value_write); zend_hash_merge(&dom_modern_element_prop_handlers, &dom_modern_node_prop_handlers, NULL, false); DOM_OVERWRITE_PROP_HANDLER(&dom_modern_element_prop_handlers, "textContent", dom_node_text_content_read, dom_node_text_content_write); zend_hash_add_new_ptr(&classes, dom_modern_element_class_entry->name, &dom_modern_element_prop_handlers); dom_html_element_class_entry = register_class_Dom_HTMLElement(dom_modern_element_class_entry); dom_html_element_class_entry->create_object = dom_objects_new; dom_html_element_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_add_new_ptr(&classes, dom_html_element_class_entry->name, &dom_modern_element_prop_handlers); dom_text_class_entry = register_class_DOMText(dom_characterdata_class_entry); dom_text_class_entry->create_object = dom_objects_new; dom_text_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_text_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_text_prop_handlers, "wholeText", dom_text_whole_text_read, NULL); zend_hash_merge(&dom_text_prop_handlers, &dom_characterdata_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_text_class_entry->name, &dom_text_prop_handlers); dom_modern_text_class_entry = register_class_Dom_Text(dom_modern_characterdata_class_entry); dom_modern_text_class_entry->create_object = dom_objects_new; dom_modern_text_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_modern_text_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_modern_text_prop_handlers, "wholeText", dom_text_whole_text_read, NULL); zend_hash_merge(&dom_modern_text_prop_handlers, &dom_modern_characterdata_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_modern_text_class_entry->name, &dom_modern_text_prop_handlers); dom_comment_class_entry = register_class_DOMComment(dom_characterdata_class_entry); dom_comment_class_entry->create_object = dom_objects_new; dom_comment_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_add_new_ptr(&classes, dom_comment_class_entry->name, &dom_characterdata_prop_handlers); dom_modern_comment_class_entry = register_class_Dom_Comment(dom_modern_characterdata_class_entry); dom_modern_comment_class_entry->create_object = dom_objects_new; dom_modern_comment_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_add_new_ptr(&classes, dom_modern_comment_class_entry->name, &dom_modern_characterdata_prop_handlers); dom_cdatasection_class_entry = register_class_DOMCdataSection(dom_text_class_entry); dom_cdatasection_class_entry->create_object = dom_objects_new; dom_cdatasection_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_add_new_ptr(&classes, dom_cdatasection_class_entry->name, &dom_text_prop_handlers); dom_modern_cdatasection_class_entry = register_class_Dom_CDATASection(dom_modern_text_class_entry); dom_modern_cdatasection_class_entry->create_object = dom_objects_new; dom_modern_cdatasection_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_add_new_ptr(&classes, dom_modern_cdatasection_class_entry->name, &dom_modern_text_prop_handlers); dom_documenttype_class_entry = register_class_DOMDocumentType(dom_node_class_entry); dom_documenttype_class_entry->create_object = dom_objects_new; dom_documenttype_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_documenttype_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "name", dom_documenttype_name_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "entities", dom_documenttype_entities_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "notations", dom_documenttype_notations_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "publicId", dom_documenttype_public_id_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "systemId", dom_documenttype_system_id_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_documenttype_prop_handlers, "internalSubset", dom_documenttype_internal_subset_read, NULL); zend_hash_merge(&dom_documenttype_prop_handlers, &dom_node_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_documenttype_class_entry->name, &dom_documenttype_prop_handlers); dom_modern_documenttype_class_entry = register_class_Dom_DocumentType(dom_modern_node_class_entry, dom_modern_childnode_class_entry); dom_modern_documenttype_class_entry->create_object = dom_objects_new; dom_modern_documenttype_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_modern_documenttype_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_modern_documenttype_prop_handlers, "name", dom_documenttype_name_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_documenttype_prop_handlers, "entities", dom_documenttype_entities_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_documenttype_prop_handlers, "notations", dom_documenttype_notations_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_documenttype_prop_handlers, "publicId", dom_documenttype_public_id_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_documenttype_prop_handlers, "systemId", dom_documenttype_system_id_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_documenttype_prop_handlers, "internalSubset", dom_documenttype_internal_subset_read, NULL); zend_hash_merge(&dom_modern_documenttype_prop_handlers, &dom_node_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_modern_documenttype_class_entry->name, &dom_modern_documenttype_prop_handlers); dom_notation_class_entry = register_class_DOMNotation(dom_node_class_entry); dom_notation_class_entry->create_object = dom_objects_new; dom_notation_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_notation_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_notation_prop_handlers, "publicId", dom_notation_public_id_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_notation_prop_handlers, "systemId", dom_notation_system_id_read, NULL); zend_hash_merge(&dom_notation_prop_handlers, &dom_node_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_notation_class_entry->name, &dom_notation_prop_handlers); dom_modern_notation_class_entry = register_class_Dom_Notation(dom_modern_node_class_entry); dom_modern_notation_class_entry->create_object = dom_objects_new; dom_modern_notation_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_modern_notation_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_modern_notation_prop_handlers, "publicId", dom_notation_public_id_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_notation_prop_handlers, "systemId", dom_notation_system_id_read, NULL); zend_hash_merge(&dom_modern_notation_prop_handlers, &dom_modern_node_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_modern_notation_class_entry->name, &dom_modern_node_prop_handlers); dom_entity_class_entry = register_class_DOMEntity(dom_node_class_entry); dom_entity_class_entry->create_object = dom_objects_new; dom_entity_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_entity_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "publicId", dom_entity_public_id_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "systemId", dom_entity_system_id_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "notationName", dom_entity_notation_name_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "actualEncoding", dom_entity_actual_encoding_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "encoding", dom_entity_encoding_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_entity_prop_handlers, "version", dom_entity_version_read, NULL); zend_hash_merge(&dom_entity_prop_handlers, &dom_node_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_entity_class_entry->name, &dom_entity_prop_handlers); dom_modern_entity_class_entry = register_class_Dom_Entity(dom_modern_node_class_entry); dom_modern_entity_class_entry->create_object = dom_objects_new; dom_modern_entity_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_modern_entity_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_modern_entity_prop_handlers, "publicId", dom_entity_public_id_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_entity_prop_handlers, "systemId", dom_entity_system_id_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_entity_prop_handlers, "notationName", dom_entity_notation_name_read, NULL); zend_hash_merge(&dom_modern_entity_prop_handlers, &dom_modern_node_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_modern_entity_class_entry->name, &dom_modern_entity_prop_handlers); dom_entityreference_class_entry = register_class_DOMEntityReference(dom_node_class_entry); dom_entityreference_class_entry->create_object = dom_objects_new; dom_entityreference_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_entity_reference_prop_handlers, 0, NULL, NULL, true); zend_hash_merge(&dom_entity_reference_prop_handlers, &dom_node_prop_handlers, NULL, false); DOM_OVERWRITE_PROP_HANDLER(&dom_entity_reference_prop_handlers, "firstChild", dom_entity_reference_child_read, NULL); DOM_OVERWRITE_PROP_HANDLER(&dom_entity_reference_prop_handlers, "lastChild", dom_entity_reference_child_read, NULL); DOM_OVERWRITE_PROP_HANDLER(&dom_entity_reference_prop_handlers, "textContent", dom_entity_reference_text_content_read, NULL); DOM_OVERWRITE_PROP_HANDLER(&dom_entity_reference_prop_handlers, "childNodes", dom_entity_reference_child_nodes_read, NULL); zend_hash_add_new_ptr(&classes, dom_entityreference_class_entry->name, &dom_entity_reference_prop_handlers); dom_modern_entityreference_class_entry = register_class_Dom_EntityReference(dom_modern_node_class_entry); dom_modern_entityreference_class_entry->create_object = dom_objects_new; dom_modern_entityreference_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_modern_entity_reference_prop_handlers, 0, NULL, NULL, true); zend_hash_merge(&dom_modern_entity_reference_prop_handlers, &dom_modern_node_prop_handlers, NULL, false); DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "firstChild", dom_entity_reference_child_read, NULL); DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "lastChild", dom_entity_reference_child_read, NULL); DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "textContent", dom_entity_reference_text_content_read, NULL); DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "childNodes", dom_entity_reference_child_nodes_read, NULL); zend_hash_add_new_ptr(&classes, dom_modern_entityreference_class_entry->name, &dom_modern_entity_reference_prop_handlers); dom_processinginstruction_class_entry = register_class_DOMProcessingInstruction(dom_node_class_entry); dom_processinginstruction_class_entry->create_object = dom_objects_new; dom_processinginstruction_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_processinginstruction_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_processinginstruction_prop_handlers, "target", dom_processinginstruction_target_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_processinginstruction_prop_handlers, "data", dom_processinginstruction_data_read, dom_processinginstruction_data_write); zend_hash_merge(&dom_processinginstruction_prop_handlers, &dom_node_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_processinginstruction_class_entry->name, &dom_processinginstruction_prop_handlers); dom_modern_processinginstruction_class_entry = register_class_Dom_ProcessingInstruction(dom_modern_characterdata_class_entry); dom_modern_processinginstruction_class_entry->create_object = dom_objects_new; dom_modern_processinginstruction_class_entry->default_object_handlers = &dom_object_handlers; zend_hash_init(&dom_modern_processinginstruction_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_modern_processinginstruction_prop_handlers, "target", dom_processinginstruction_target_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_modern_processinginstruction_prop_handlers, "data", dom_processinginstruction_data_read, dom_processinginstruction_data_write); zend_hash_merge(&dom_modern_processinginstruction_prop_handlers, &dom_modern_characterdata_prop_handlers, NULL, false); zend_hash_add_new_ptr(&classes, dom_modern_processinginstruction_class_entry->name, &dom_modern_processinginstruction_prop_handlers); #ifdef LIBXML_XPATH_ENABLED memcpy(&dom_xpath_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers)); dom_xpath_object_handlers.offset = XtOffsetOf(dom_xpath_object, dom) + XtOffsetOf(dom_object, std); dom_xpath_object_handlers.free_obj = dom_xpath_objects_free_storage; dom_xpath_object_handlers.get_gc = dom_xpath_get_gc; dom_xpath_object_handlers.clone_obj = NULL; dom_xpath_class_entry = register_class_DOMXPath(); dom_xpath_class_entry->create_object = dom_xpath_objects_new; dom_xpath_class_entry->default_object_handlers = &dom_xpath_object_handlers; zend_hash_init(&dom_xpath_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_xpath_prop_handlers, "document", dom_xpath_document_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_xpath_prop_handlers, "registerNodeNamespaces", dom_xpath_register_node_ns_read, dom_xpath_register_node_ns_write); zend_hash_add_new_ptr(&classes, dom_xpath_class_entry->name, &dom_xpath_prop_handlers); dom_modern_xpath_class_entry = register_class_Dom_XPath(); dom_modern_xpath_class_entry->create_object = dom_xpath_objects_new; dom_modern_xpath_class_entry->default_object_handlers = &dom_xpath_object_handlers; zend_hash_add_new_ptr(&classes, dom_modern_xpath_class_entry->name, &dom_xpath_prop_handlers); #endif dom_token_list_class_entry = register_class_Dom_TokenList(zend_ce_aggregate, zend_ce_countable); dom_token_list_class_entry->create_object = dom_token_list_new; dom_token_list_class_entry->default_object_handlers = &dom_token_list_object_handlers; dom_token_list_class_entry->get_iterator = dom_token_list_get_iterator; zend_hash_init(&dom_token_list_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_token_list_prop_handlers, "length", dom_token_list_length_read, NULL); DOM_REGISTER_PROP_HANDLER(&dom_token_list_prop_handlers, "value", dom_token_list_value_read, dom_token_list_value_write); zend_hash_add_new_ptr(&classes, dom_token_list_class_entry->name, &dom_token_list_prop_handlers); register_php_dom_symbols(module_number); php_libxml_register_export(dom_node_class_entry, php_dom_export_node); php_libxml_register_export(dom_modern_node_class_entry, php_dom_export_node); lexbor_memory_setup(dom_malloc, dom_realloc, dom_calloc, dom_free); return SUCCESS; } /* }}} */ /* {{{ */ PHP_MINFO_FUNCTION(dom) { php_info_print_table_start(); php_info_print_table_row(2, "DOM/XML", "enabled"); php_info_print_table_row(2, "DOM/XML API Version", DOM_API_VERSION); php_info_print_table_row(2, "libxml Version", LIBXML_DOTTED_VERSION); #ifdef LIBXML_HTML_ENABLED php_info_print_table_row(2, "HTML Support", "enabled"); #endif #ifdef LIBXML_XPATH_ENABLED php_info_print_table_row(2, "XPath Support", "enabled"); #endif #ifdef LIBXML_XPTR_ENABLED php_info_print_table_row(2, "XPointer Support", "enabled"); #endif #ifdef LIBXML_SCHEMAS_ENABLED php_info_print_table_row(2, "Schema Support", "enabled"); php_info_print_table_row(2, "RelaxNG Support", "enabled"); #endif php_info_print_table_end(); } /* }}} */ PHP_MSHUTDOWN_FUNCTION(dom) /* {{{ */ { zend_hash_destroy(&dom_document_prop_handlers); zend_hash_destroy(&dom_abstract_base_document_prop_handlers); zend_hash_destroy(&dom_xml_document_prop_handlers); zend_hash_destroy(&dom_documentfragment_prop_handlers); zend_hash_destroy(&dom_modern_documentfragment_prop_handlers); zend_hash_destroy(&dom_node_prop_handlers); zend_hash_destroy(&dom_modern_node_prop_handlers); zend_hash_destroy(&dom_entity_reference_prop_handlers); zend_hash_destroy(&dom_modern_entity_reference_prop_handlers); zend_hash_destroy(&dom_namespace_node_prop_handlers); zend_hash_destroy(&dom_nodelist_prop_handlers); zend_hash_destroy(&dom_namednodemap_prop_handlers); zend_hash_destroy(&dom_characterdata_prop_handlers); zend_hash_destroy(&dom_modern_characterdata_prop_handlers); zend_hash_destroy(&dom_attr_prop_handlers); zend_hash_destroy(&dom_modern_attr_prop_handlers); zend_hash_destroy(&dom_element_prop_handlers); zend_hash_destroy(&dom_modern_element_prop_handlers); zend_hash_destroy(&dom_text_prop_handlers); zend_hash_destroy(&dom_modern_text_prop_handlers); zend_hash_destroy(&dom_documenttype_prop_handlers); zend_hash_destroy(&dom_modern_documenttype_prop_handlers); zend_hash_destroy(&dom_notation_prop_handlers); zend_hash_destroy(&dom_modern_notation_prop_handlers); zend_hash_destroy(&dom_entity_prop_handlers); zend_hash_destroy(&dom_modern_entity_prop_handlers); zend_hash_destroy(&dom_processinginstruction_prop_handlers); zend_hash_destroy(&dom_modern_processinginstruction_prop_handlers); zend_hash_destroy(&dom_token_list_prop_handlers); #ifdef LIBXML_XPATH_ENABLED zend_hash_destroy(&dom_xpath_prop_handlers); #endif zend_hash_destroy(&classes); /* If you want do find memleaks in this module, compile libxml2 with --with-mem-debug and uncomment the following line, this will tell you the amount of not freed memory and the total used memory into apaches error_log */ /* xmlMemoryDump();*/ return SUCCESS; } /* }}} */ /* {{{ node_list_unlink */ void node_list_unlink(xmlNodePtr node) { dom_object *wrapper; while (node != NULL) { wrapper = php_dom_object_get_data(node); if (wrapper != NULL ) { xmlUnlinkNode(node); } else { if (node->type == XML_ENTITY_REF_NODE) break; node_list_unlink(node->children); switch (node->type) { case XML_ATTRIBUTE_DECL: case XML_DTD_NODE: case XML_DOCUMENT_TYPE_NODE: case XML_ENTITY_DECL: case XML_ATTRIBUTE_NODE: case XML_TEXT_NODE: break; default: node_list_unlink((xmlNodePtr) node->properties); } } node = node->next; } } /* }}} end node_list_unlink */ /* {{{ dom_objects_free_storage */ void dom_objects_free_storage(zend_object *object) { dom_object *intern = php_dom_obj_from_obj(object); zend_object_std_dtor(&intern->std); php_libxml_node_ptr *ptr = intern->ptr; if (ptr != NULL && ptr->node != NULL) { xmlNodePtr node = ptr->node; if (node->type != XML_DOCUMENT_NODE && node->type != XML_HTML_DOCUMENT_NODE) { php_libxml_node_decrement_resource((php_libxml_node_object *) intern); } else { php_libxml_decrement_node_ptr((php_libxml_node_object *) intern); php_libxml_decrement_doc_ref((php_libxml_node_object *) intern); } intern->ptr = NULL; } } /* }}} */ void dom_namednode_iter(dom_object *basenode, int ntype, dom_object *intern, xmlHashTablePtr ht, const char *local, size_t local_len, const char *ns, size_t ns_len) /* {{{ */ { dom_nnodemap_object *mapptr = (dom_nnodemap_object *) intern->ptr; ZEND_ASSERT(basenode != NULL); ZVAL_OBJ_COPY(&mapptr->baseobj_zv, &basenode->std); xmlDocPtr doc = basenode->document ? basenode->document->ptr : NULL; mapptr->baseobj = basenode; mapptr->nodetype = ntype; mapptr->ht = ht; const xmlChar* tmp; if (local) { int len = local_len > INT_MAX ? -1 : (int) local_len; if (doc != NULL && (tmp = xmlDictExists(doc->dict, (const xmlChar *)local, len)) != NULL) { mapptr->local = BAD_CAST tmp; } else { mapptr->local = xmlCharStrndup(local, len); mapptr->free_local = true; } mapptr->local_lower = BAD_CAST estrdup(local); if (len < 0) { zend_str_tolower((char *) mapptr->local_lower, strlen((const char *) mapptr->local_lower)); } else { zend_str_tolower((char *) mapptr->local_lower, len); } } if (ns) { int len = ns_len > INT_MAX ? -1 : (int) ns_len; if (doc != NULL && (tmp = xmlDictExists(doc->dict, (const xmlChar *)ns, len)) != NULL) { mapptr->ns = BAD_CAST tmp; } else { mapptr->ns = xmlCharStrndup(ns, len); mapptr->free_ns = true; } } } /* }}} */ static void dom_objects_set_class_ex(zend_class_entry *class_type, dom_object *intern) { zend_class_entry *base_class = class_type; while ((base_class->type != ZEND_INTERNAL_CLASS || base_class->info.internal.module->module_number != dom_module_entry.module_number) && base_class->parent != NULL) { base_class = base_class->parent; } intern->prop_handler = zend_hash_find_ptr(&classes, base_class->name); zend_object_std_init(&intern->std, class_type); object_properties_init(&intern->std, class_type); } static dom_object* dom_objects_set_class(zend_class_entry *class_type) { dom_object *intern = zend_object_alloc(sizeof(dom_object), class_type); dom_objects_set_class_ex(class_type, intern); return intern; } /* {{{ dom_objects_new */ zend_object *dom_objects_new(zend_class_entry *class_type) { dom_object *intern = dom_objects_set_class(class_type); return &intern->std; } /* }}} */ static zend_object *dom_objects_namespace_node_new(zend_class_entry *class_type) { dom_object_namespace_node *intern = zend_object_alloc(sizeof(dom_object_namespace_node), class_type); dom_objects_set_class_ex(class_type, &intern->dom); return &intern->dom.std; } static void dom_object_namespace_node_free_storage(zend_object *object) { dom_object_namespace_node *intern = php_dom_namespace_node_obj_from_obj(object); if (intern->parent_intern != NULL) { OBJ_RELEASE(&intern->parent_intern->std); } dom_objects_free_storage(object); } #ifdef LIBXML_XPATH_ENABLED /* {{{ zend_object dom_xpath_objects_new(zend_class_entry *class_type) */ zend_object *dom_xpath_objects_new(zend_class_entry *class_type) { dom_xpath_object *intern = zend_object_alloc(sizeof(dom_xpath_object), class_type); php_dom_xpath_callbacks_ctor(&intern->xpath_callbacks); intern->register_node_ns = true; intern->dom.prop_handler = &dom_xpath_prop_handlers; zend_object_std_init(&intern->dom.std, class_type); object_properties_init(&intern->dom.std, class_type); return &intern->dom.std; } /* }}} */ #endif void dom_nnodemap_objects_free_storage(zend_object *object) /* {{{ */ { dom_object *intern = php_dom_obj_from_obj(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->free_local) { xmlFree(objmap->local); } if (objmap->free_ns) { xmlFree(objmap->ns); } if (objmap->local_lower) { efree(objmap->local_lower); } if (!Z_ISUNDEF(objmap->baseobj_zv)) { zval_ptr_dtor(&objmap->baseobj_zv); } efree(objmap); intern->ptr = NULL; } php_libxml_decrement_doc_ref((php_libxml_node_object *)intern); zend_object_std_dtor(&intern->std); } /* }}} */ zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type) { dom_object *intern; dom_nnodemap_object *objmap; intern = dom_objects_set_class(class_type); intern->ptr = emalloc(sizeof(dom_nnodemap_object)); objmap = (dom_nnodemap_object *)intern->ptr; ZVAL_UNDEF(&objmap->baseobj_zv); objmap->baseobj = NULL; objmap->nodetype = 0; objmap->ht = NULL; objmap->local = NULL; objmap->local_lower = NULL; objmap->free_local = false; objmap->ns = NULL; objmap->free_ns = false; objmap->cache_tag.modification_nr = 0; objmap->cached_length = -1; objmap->cached_obj = NULL; objmap->cached_obj_index = 0; return &intern->std; } void php_dom_create_iterator(zval *return_value, dom_iterator_type iterator_type, bool modern) /* {{{ */ { zend_class_entry *ce; if (iterator_type == DOM_NAMEDNODEMAP) { ce = dom_get_namednodemap_ce(modern); } else if (iterator_type == DOM_HTMLCOLLECTION) { /* This only exists in modern DOM. */ ZEND_ASSERT(modern); ce = dom_html_collection_class_entry; } else if (iterator_type == DOM_DTD_NAMEDNODEMAP) { ce = dom_get_dtd_namednodemap_ce(modern); } else { ZEND_ASSERT(iterator_type == DOM_NODELIST); ce = dom_get_nodelist_ce(modern); } object_init_ex(return_value, ce); } /* }}} */ static zend_always_inline zend_class_entry *dom_get_element_ce(const xmlNode *node, bool modern) { if (modern) { if (php_dom_ns_is_fast(node, php_dom_ns_is_html_magic_token)) { return dom_html_element_class_entry; } else { return dom_modern_element_class_entry; } } else { return dom_element_class_entry; } } bool php_dom_create_nullable_object(xmlNodePtr obj, zval *return_value, dom_object *domobj) { if (!obj) { ZVAL_NULL(return_value); return false; } return php_dom_create_object(obj, return_value, domobj); } /* {{{ php_dom_create_object */ PHP_DOM_EXPORT bool php_dom_create_object(xmlNodePtr obj, zval *return_value, dom_object *domobj) { dom_object *intern = php_dom_object_get_data(obj); if (intern) { ZVAL_OBJ_COPY(return_value, &intern->std); return true; } bool modern = domobj && php_dom_follow_spec_intern(domobj); zend_class_entry *ce; switch (obj->type) { case XML_DOCUMENT_NODE: { ce = dom_get_xml_document_ce(modern); break; } case XML_HTML_DOCUMENT_NODE: { ce = dom_get_html_document_ce(modern); break; } case XML_DTD_NODE: case XML_DOCUMENT_TYPE_NODE: { ce = dom_get_documenttype_ce(modern); break; } case XML_ELEMENT_NODE: { ce = dom_get_element_ce(obj, modern); break; } case XML_ATTRIBUTE_NODE: { ce = dom_get_attr_ce(modern); break; } case XML_TEXT_NODE: { ce = dom_get_text_ce(modern); break; } case XML_COMMENT_NODE: { ce = dom_get_comment_ce(modern); break; } case XML_PI_NODE: { ce = dom_get_processinginstruction_ce(modern); break; } case XML_ENTITY_REF_NODE: { ce = dom_get_entityreference_ce(modern); break; } case XML_ENTITY_DECL: case XML_ELEMENT_DECL: { ce = dom_get_entity_ce(modern); break; } case XML_CDATA_SECTION_NODE: { ce = dom_get_cdatasection_ce(modern); break; } case XML_DOCUMENT_FRAG_NODE: { ce = dom_get_documentfragment_ce(modern); break; } case XML_NOTATION_NODE: { ce = dom_get_notation_ce(modern); break; } case XML_NAMESPACE_DECL: { /* This has no modern equivalent */ ce = dom_namespace_node_class_entry; break; } default: /* TODO you can actually hit this with fixed attributes in the DTD for example... */ zend_throw_error(NULL, "Unsupported node type: %d", obj->type); ZVAL_NULL(return_value); return false; } if (domobj && domobj->document) { ce = dom_get_doc_classmap(domobj->document, ce); } php_dom_instantiate_object_helper(return_value, ce, obj, domobj); return false; } /* }}} end php_domobject_new */ dom_object *php_dom_instantiate_object_helper(zval *return_value, zend_class_entry *ce, xmlNodePtr obj, dom_object *parent) { object_init_ex(return_value, ce); dom_object *intern = Z_DOMOBJ_P(return_value); if (obj->doc != NULL) { if (parent != NULL) { intern->document = parent->document; } php_libxml_increment_doc_ref((php_libxml_node_object *)intern, obj->doc); } php_libxml_increment_node_ptr((php_libxml_node_object *)intern, obj, (void *)intern); return intern; } void php_dom_create_implementation(zval *retval, bool modern) { object_init_ex(retval, dom_get_domimplementation_ce(modern)); } /* {{{ int dom_hierarchy(xmlNodePtr parent, xmlNodePtr child) */ int dom_hierarchy(xmlNodePtr parent, xmlNodePtr child) { xmlNodePtr nodep; if (parent == NULL || child == NULL || child->doc != parent->doc) { return SUCCESS; } if (child->type == XML_DOCUMENT_NODE) { return FAILURE; } nodep = parent; while (nodep) { if (nodep == child) { return FAILURE; } nodep = nodep->parent; } return SUCCESS; } /* }}} end dom_hierarchy */ /* {{{ */ bool dom_has_feature(zend_string *feature, zend_string *version) { if (zend_string_equals_literal(version, "1.0") || zend_string_equals_literal(version, "2.0") || zend_string_equals_literal(version, "") ) { if (zend_string_equals_literal_ci(feature, "XML") || (zend_string_equals_literal_ci(feature, "Core") && zend_string_equals_literal(version, "1.0")) ) { return true; } } return false; } /* }}} end dom_has_feature */ bool dom_match_qualified_name_according_to_spec(const xmlChar *qname, const xmlNode *nodep) { const xmlChar *node_local = nodep->name; /* The qualified name must be matched, which means either: * - The local parts are equal and there is no ns prefix for this element (i.e. the fqn is in the local name). * - There is a prefix, the prefixes are equal and the local parts are equal. */ if (nodep->ns != NULL && nodep->ns->prefix != NULL) { const char *prefix = (const char *) nodep->ns->prefix; /* 1. match prefix up to |prefix| characters. * This won't overflow as it'll stop at the '\0' if the lengths don't match. */ size_t prefix_len = strlen(prefix); if (strncmp((const char *) qname, prefix, prefix_len) != 0) { return false; } /* 2. match ':' */ if (qname[prefix_len] != ':') { return false; } /* 3. match local name */ return xmlStrEqual(qname + prefix_len + 1, node_local); } else { return xmlStrEqual(node_local, qname); } } static bool dom_match_qualified_name_for_tag_name_equality(const xmlChar *local, const xmlChar *local_lower, const xmlNode *nodep, bool match_qname) { if (!match_qname) { return xmlStrEqual(nodep->name, local); } const xmlChar *local_to_use = nodep->doc->type == XML_HTML_DOCUMENT_NODE && php_dom_ns_is_fast(nodep, php_dom_ns_is_html_magic_token) ? local_lower : local; return dom_match_qualified_name_according_to_spec(local_to_use, nodep); } xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr basep, xmlNodePtr nodep, xmlChar *ns, xmlChar *local, xmlChar *local_lower, zend_long *cur, zend_long index) /* {{{ */ { /* Can happen with detached document */ if (UNEXPECTED(nodep == NULL)) { return NULL; } xmlNodePtr ret = NULL; bool local_match_any = local[0] == '*' && local[1] == '\0'; /* Note: The spec says that ns == '' must be transformed to ns == NULL. In other words, they are equivalent. * PHP however does not do this and internally uses the empty string everywhere when the user provides ns == NULL. * This is because for PHP ns == NULL has another meaning: "match every namespace" instead of "match the empty namespace". */ bool ns_match_any = ns == NULL || (ns[0] == '*' && ns[1] == '\0'); bool match_qname = ns == NULL && php_dom_follow_spec_node(basep); while (*cur <= index) { if (nodep->type == XML_ELEMENT_NODE) { if (local_match_any || dom_match_qualified_name_for_tag_name_equality(local, local_lower, nodep, match_qname)) { if (ns_match_any || (ns[0] == '\0' && nodep->ns == NULL) || (nodep->ns != NULL && xmlStrEqual(nodep->ns->href, ns))) { if (*cur == index) { ret = nodep; break; } (*cur)++; } } } nodep = php_dom_next_in_tree_order(nodep, basep); if (!nodep) { return NULL; } } return ret; } /* }}} end dom_element_get_elements_by_tag_name_ns_raw */ static inline bool is_empty_node(xmlNodePtr nodep) { return nodep->content == NULL || *nodep->content == '\0'; } static inline void free_node(xmlNodePtr node) { if (node->_private == NULL) { xmlFreeNode(node); } } static void dom_merge_adjacent_exclusive_text_nodes(xmlNodePtr node) { xmlNodePtr nextp = node->next; while (nextp != NULL && nextp->type == XML_TEXT_NODE) { xmlNodePtr newnextp = nextp->next; xmlChar *strContent = nextp->content; if (strContent != NULL) { xmlNodeAddContent(node, strContent); } xmlUnlinkNode(nextp); free_node(nextp); nextp = newnextp; } } /* {{{ void php_dom_normalize_legacy(xmlNodePtr nodep) */ void php_dom_normalize_legacy(xmlNodePtr nodep) { xmlNodePtr child = nodep->children; while(child != NULL) { switch (child->type) { case XML_TEXT_NODE: dom_merge_adjacent_exclusive_text_nodes(child); if (is_empty_node(child)) { xmlNodePtr nextp = child->next; xmlUnlinkNode(child); free_node(child); child = nextp; continue; } break; case XML_ELEMENT_NODE: php_dom_normalize_legacy(child); xmlAttrPtr attr = child->properties; while (attr != NULL) { php_dom_normalize_legacy((xmlNodePtr) attr); attr = attr->next; } break; default: break; } child = child->next; } } /* }}} end php_dom_normalize_legacy */ /* https://dom.spec.whatwg.org/#dom-node-normalize */ void php_dom_normalize_modern(xmlNodePtr this) { /* for each descendant exclusive Text node node of this: */ xmlNodePtr node = this->children; while (node != NULL) { if (node->type == XML_TEXT_NODE) { /* 1. Let length be node’s length. * We'll deviate a bit here: we'll just check if it's empty or not as we don't want to compute the length. */ bool is_empty = is_empty_node(node); /* 2. If length is zero, then remove node and continue with the next exclusive Text node, if any. */ if (is_empty) { xmlNodePtr next = node->next; xmlUnlinkNode(node); free_node(node); node = next; continue; } /* 3. Let data be the concatenation of the data of node’s contiguous exclusive Text nodes (excluding itself), in tree order. * 4. Replace data with node node, offset length, count 0, and data data. * 7. Remove node’s contiguous exclusive Text nodes (excluding itself), in tree order. * => In other words: Concat every contiguous text node into node and delete the merged nodes. */ dom_merge_adjacent_exclusive_text_nodes(node); /* Steps 5-6 deal with mutation records, we don't do that here. */ } else if (node->type == XML_ELEMENT_NODE) { php_dom_normalize_modern(node); } node = node->next; } } static void dom_reconcile_ns_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr search_parent) { xmlNsPtr nsptr, nsdftptr, curns, prevns = NULL; /* Following if block primarily used for inserting nodes created via createElementNS */ if (nodep->nsDef != NULL) { curns = nodep->nsDef; while (curns) { nsdftptr = curns->next; if (curns->href != NULL) { if((nsptr = xmlSearchNsByHref(doc, search_parent, curns->href)) && (curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) { curns->next = NULL; if (prevns == NULL) { nodep->nsDef = nsdftptr; } else { prevns->next = nsdftptr; } /* Note: we can't get here if the ns is already on the oldNs list. * This is because in that case the definition won't be on the node, and * therefore won't be in the nodep->nsDef list. */ php_libxml_set_old_ns(doc, curns); curns = prevns; } } prevns = curns; curns = nsdftptr; } } } void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */ { ZEND_ASSERT(nodep->type != XML_ATTRIBUTE_NODE); /* Although the node type will be checked by the libxml2 API, * we still want to do the internal reconciliation conditionally. */ if (nodep->type == XML_ELEMENT_NODE) { dom_reconcile_ns_internal(doc, nodep, nodep->parent); xmlReconciliateNs(doc, nodep); } } /* }}} */ static void dom_reconcile_ns_list_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last, xmlNodePtr search_parent) { ZEND_ASSERT(nodep != NULL); while (true) { if (nodep->type == XML_ELEMENT_NODE) { dom_reconcile_ns_internal(doc, nodep, search_parent); if (nodep->children) { dom_reconcile_ns_list_internal(doc, nodep->children, nodep->last /* process the whole children list */, search_parent); } } if (nodep == last) { break; } nodep = nodep->next; } } void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last) { dom_reconcile_ns_list_internal(doc, nodep, last, nodep->parent); /* The loop is outside of the recursion in the above call because * dom_libxml_reconcile_ensure_namespaces_are_declared() performs its own recursion. */ while (true) { /* The internal libxml2 call will already check the node type, no need for us to do it here. */ xmlReconciliateNs(doc, nodep); if (nodep == last) { break; } nodep = nodep->next; } } /* https://dom.spec.whatwg.org/#validate-and-extract */ int dom_validate_and_extract(const zend_string *namespace, const zend_string *qname, xmlChar **localName, xmlChar **prefix) { /* 1. If namespace is the empty string, then set it to null. * However, we're going to cheat and do the opposite to make * implementation of the below steps with existing zend_ helpers easier. */ if (namespace == NULL) { namespace = zend_empty_string; } /* 2. Validate qualifiedName. */ if (xmlValidateQName(BAD_CAST ZSTR_VAL(qname), /* allow spaces */ 0) != 0) { return INVALID_CHARACTER_ERR; } /* Steps 3-5 */ *localName = xmlSplitQName2(BAD_CAST ZSTR_VAL(qname), prefix); /* 6. If prefix is non-null and namespace is null, then throw a "NamespaceError" DOMException. * Note that null namespace means empty string here becaue of step 1. */ if (*prefix != NULL && ZSTR_VAL(namespace)[0] == '\0') { return NAMESPACE_ERR; } /* 7. If prefix is "xml" and namespace is not the XML namespace, then throw a "NamespaceError" DOMException. */ if (UNEXPECTED(!zend_string_equals_literal(namespace, "http://www.w3.org/XML/1998/namespace") && xmlStrEqual(*prefix, BAD_CAST "xml"))) { return NAMESPACE_ERR; } /* 8. If either qualifiedName or prefix is "xmlns" and namespace is not the XMLNS namespace, then throw a "NamespaceError" DOMException. */ if (UNEXPECTED((zend_string_equals_literal(qname, "xmlns") || xmlStrEqual(*prefix, BAD_CAST "xmlns")) && !zend_string_equals_literal(namespace, "http://www.w3.org/2000/xmlns/"))) { return NAMESPACE_ERR; } /* 9. If namespace is the XMLNS namespace and neither qualifiedName nor prefix is "xmlns", then throw a "NamespaceError" DOMException. */ if (UNEXPECTED(zend_string_equals_literal(namespace, "http://www.w3.org/2000/xmlns/") && !zend_string_equals_literal(qname, "xmlns") && !xmlStrEqual(*prefix, BAD_CAST "xmlns"))) { return NAMESPACE_ERR; } if (*localName == NULL) { *localName = xmlStrdup(BAD_CAST ZSTR_VAL(qname)); } return 0; } /* http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS NAMESPACE_ERR: Raised if 1. the qualifiedName is a malformed qualified name 2. the qualifiedName has a prefix and the namespaceURI is null */ /* {{{ int dom_check_qname(char *qname, char **localname, char **prefix, int uri_len, int name_len) */ int dom_check_qname(char *qname, char **localname, char **prefix, int uri_len, int name_len) { if (name_len == 0) { return NAMESPACE_ERR; } *localname = (char *)xmlSplitQName2(BAD_CAST qname, (xmlChar **) prefix); if (*localname == NULL) { *localname = (char *)xmlStrdup(BAD_CAST qname); if (*prefix == NULL && uri_len == 0) { return 0; } } /* 1 */ if (xmlValidateQName(BAD_CAST qname, 0) != 0) { return NAMESPACE_ERR; } /* 2 */ if (*prefix != NULL && uri_len == 0) { return NAMESPACE_ERR; } return 0; } /* }}} */ /* Creates a new namespace declaration with a random prefix with the given uri on the tree. * This is used to resolve a namespace prefix conflict in cases where spec does not want a * namespace error in case of conflicts, but demands a resolution. */ xmlNsPtr dom_get_ns_resolve_prefix_conflict(xmlNodePtr tree, const char *uri) { ZEND_ASSERT(tree != NULL); xmlDocPtr doc = tree->doc; if (UNEXPECTED(doc == NULL)) { return NULL; } /* Code adapted from libxml2 (2.10.4) */ char prefix[50]; int counter = 1; snprintf(prefix, sizeof(prefix), "default"); xmlNsPtr nsptr = xmlSearchNs(doc, tree, (const xmlChar *) prefix); while (nsptr != NULL) { if (counter > 1000) { return NULL; } snprintf(prefix, sizeof(prefix), "default%d", counter++); nsptr = xmlSearchNs(doc, tree, (const xmlChar *) prefix); } /* Search yielded no conflict */ return xmlNewNs(tree, (const xmlChar *) uri, (const xmlChar *) prefix); } /* http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS NAMESPACE_ERR: Raised if 3. the qualifiedName has a prefix that is "xml" and the namespaceURI is different from "http://www.w3.org/XML/1998/namespace" [XML Namespaces] 4. the qualifiedName or its prefix is "xmlns" and the namespaceURI is different from "http://www.w3.org/2000/xmlns/" 5. the namespaceURI is "http://www.w3.org/2000/xmlns/" and neither the qualifiedName nor its prefix is "xmlns". */ xmlNsPtr dom_get_ns_unchecked(xmlNodePtr nodep, char *uri, char *prefix) { xmlNsPtr nsptr = xmlNewNs(nodep, BAD_CAST uri, BAD_CAST prefix); if (UNEXPECTED(nsptr == NULL)) { /* Either memory allocation failure, or it's because of a prefix conflict. * We'll assume a conflict and try again. If it was a memory allocation failure we'll just fail again, whatever. * This isn't needed for every caller (such as createElementNS & DOMElement::__construct), but isn't harmful and simplifies the mental model "when do I use which function?". * This branch will also be taken unlikely anyway as in those cases it'll be for allocation failure. */ return dom_get_ns_resolve_prefix_conflict(nodep, uri); } return nsptr; } /* {{{ xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) */ xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) { xmlNsPtr nsptr; if (! ((prefix && !strcmp (prefix, "xml") && strcmp(uri, (char *)XML_XML_NAMESPACE)) || (prefix && !strcmp (prefix, "xmlns") && strcmp(uri, DOM_XMLNS_NS_URI)) || (prefix && !strcmp(uri, DOM_XMLNS_NS_URI) && strcmp (prefix, "xmlns")))) { nsptr = dom_get_ns_unchecked(nodep, uri, prefix); if (UNEXPECTED(nsptr == NULL)) { goto err; } } else { goto err; } *errorcode = 0; return nsptr; err: *errorcode = NAMESPACE_ERR; return NULL; } /* }}} end dom_get_ns */ /* {{{ xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName) */ xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName) { xmlNsPtr cur; xmlNs *ret = NULL; if (node == NULL) return NULL; if (localName == NULL || localName[0] == '\0') { cur = node->nsDef; while (cur != NULL) { if (cur->prefix == NULL && cur->href != NULL) { ret = cur; break; } cur = cur->next; } } else { cur = node->nsDef; while (cur != NULL) { if (cur->prefix != NULL && xmlStrEqual(localName, cur->prefix)) { ret = cur; break; } cur = cur->next; } } return ret; } /* }}} end dom_get_nsdecl */ static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original) { xmlNodePtr attrp; xmlNsPtr curns = xmlNewNs(NULL, original->href, NULL); if (original->prefix) { curns->prefix = xmlStrdup(original->prefix); attrp = xmlNewDocNode(nodep->doc, NULL, BAD_CAST original->prefix, original->href); } else { attrp = xmlNewDocNode(nodep->doc, NULL, BAD_CAST "xmlns", original->href); } attrp->type = XML_NAMESPACE_DECL; attrp->parent = nodep; attrp->ns = curns; return attrp; } /* Note: Assumes the additional lifetime was already added in the caller. */ xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr original, zval *return_value, dom_object *parent_intern) { xmlNodePtr attrp = php_dom_create_fake_namespace_decl_node_ptr(nodep, original); php_dom_create_object(attrp, return_value, parent_intern); /* This object must exist, because we just created an object for it via php_dom_create_object(). */ php_dom_namespace_node_obj_from_obj(Z_OBJ_P(return_value))->parent_intern = parent_intern; return attrp; } static bool dom_nodemap_or_nodelist_process_offset_as_named(zval *offset, zend_long *lval) { if (Z_TYPE_P(offset) == IS_STRING) { /* See zval_get_long_func() */ double dval; zend_uchar is_numeric_string_type; if (0 == (is_numeric_string_type = is_numeric_string(Z_STRVAL_P(offset), Z_STRLEN_P(offset), lval, &dval, true))) { return true; } else if (is_numeric_string_type == IS_DOUBLE) { *lval = zend_dval_to_lval_cap(dval); } } else { *lval = zval_get_long(offset); } return false; } static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv) { if (UNEXPECTED(!offset)) { zend_throw_error(NULL, "Cannot access %s without offset", ZSTR_VAL(object->ce->name)); return NULL; } ZVAL_DEREF(offset); zend_long lval; if (dom_nodemap_or_nodelist_process_offset_as_named(offset, &lval)) { /* does not support named lookup */ ZVAL_NULL(rv); return rv; } php_dom_nodelist_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, lval, rv); return rv; } static int dom_nodelist_has_dimension(zend_object *object, zval *member, int check_empty) { ZVAL_DEREF(member); /* If it exists, it cannot be empty because nodes aren't empty. */ ZEND_IGNORE_VALUE(check_empty); zend_long offset; if (dom_nodemap_or_nodelist_process_offset_as_named(member, &offset)) { /* does not support named lookup */ return 0; } return offset >= 0 && offset < php_dom_get_nodelist_length(php_dom_obj_from_obj(object)); } void dom_remove_all_children(xmlNodePtr nodep) { if (nodep->children) { node_list_unlink(nodep->children); php_libxml_node_free_list((xmlNodePtr) nodep->children); nodep->children = NULL; nodep->last = NULL; } } void php_dom_get_content_into_zval(const xmlNode *nodep, zval *return_value, bool null_on_failure) { ZEND_ASSERT(nodep != NULL); switch (nodep->type) { case XML_TEXT_NODE: case XML_CDATA_SECTION_NODE: case XML_PI_NODE: case XML_COMMENT_NODE: { char *str = (char * ) nodep->content; if (str != NULL) { RETURN_STRING(str); } break; } case XML_ATTRIBUTE_NODE: { bool free; xmlChar *value = php_libxml_attr_value((const xmlAttr *) nodep, &free); RETURN_STRING_FAST((const char *) value); if (free) { xmlFree(value); } return; } default: { char *str = (char *) xmlNodeGetContent(nodep); if (str != NULL) { RETVAL_STRING(str); xmlFree(str); return; } break; } } if (null_on_failure) { RETURN_NULL(); } else { RETURN_EMPTY_STRING(); } } static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv) { if (UNEXPECTED(!offset)) { zend_throw_error(NULL, "Cannot access %s without offset", ZSTR_VAL(object->ce->name)); return NULL; } ZVAL_DEREF(offset); 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); return rv; } /* see PHP_METHOD(DOMNamedNodeMap, item) */ if (UNEXPECTED(lval < 0 || ZEND_LONG_INT_OVFL(lval))) { zend_value_error("must be between 0 and %d", INT_MAX); return NULL; } php_dom_named_node_map_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, lval, rv); return rv; } static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty) { ZVAL_DEREF(member); /* If it exists, it cannot be empty because nodes aren't empty. */ ZEND_IGNORE_VALUE(check_empty); 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; } return offset >= 0 && offset < php_dom_get_namednodemap_length(php_dom_obj_from_obj(object)); } static zval *dom_modern_nodemap_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_nnodemap_object *map = php_dom_obj_from_obj(object)->ptr; ZVAL_DEREF(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); } else { php_dom_named_node_map_get_named_item_into_zval(map, Z_STR_P(offset), rv); } } else if (Z_TYPE_P(offset) == IS_LONG) { php_dom_named_node_map_get_item_into_zval(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); } else { zend_illegal_container_offset(object->ce->name, offset, type); return NULL; } return rv; } static int dom_modern_nodemap_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_object *obj = php_dom_obj_from_obj(object); dom_nnodemap_object *map = obj->ptr; ZVAL_DEREF(member); if (Z_TYPE_P(member) == IS_STRING) { zend_ulong lval; 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; } } else if (Z_TYPE_P(member) == IS_LONG) { zend_long offset = Z_LVAL_P(member); return offset >= 0 && offset < php_dom_get_namednodemap_length(obj); } else if (Z_TYPE_P(member) == IS_DOUBLE) { zend_long offset = zend_dval_to_lval_safe(Z_DVAL_P(member)); return offset >= 0 && offset < php_dom_get_namednodemap_length(obj); } else { zend_illegal_container_offset(object->ce->name, member, BP_VAR_IS); return 0; } } static xmlNodePtr dom_clone_container_helper(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr src_node, xmlDocPtr dst_doc) { xmlNodePtr clone = xmlDocCopyNode(src_node, dst_doc, 0); if (EXPECTED(clone != NULL)) { /* Set namespace to the original, reconciliation will fix this up. */ clone->ns = src_node->ns; if (src_node->type == XML_ELEMENT_NODE) { xmlAttrPtr last_added_attr = NULL; if (src_node->nsDef != NULL) { xmlNsPtr current_ns = src_node->nsDef; do { php_dom_ns_compat_mark_attribute(ns_mapper, clone, current_ns); } while ((current_ns = current_ns->next) != NULL); last_added_attr = clone->properties; while (last_added_attr->next != NULL) { last_added_attr = last_added_attr->next; } } /* Attribute cloning logic. */ for (xmlAttrPtr attr = src_node->properties; attr != NULL; attr = attr->next) { xmlAttrPtr new_attr = (xmlAttrPtr) xmlDocCopyNode((xmlNodePtr) attr, dst_doc, 0); if (UNEXPECTED(new_attr == NULL)) { xmlFreeNode(clone); return NULL; } if (last_added_attr == NULL) { clone->properties = new_attr; } else { new_attr->prev = last_added_attr; last_added_attr->next = new_attr; } new_attr->parent = clone; last_added_attr = new_attr; /* Set namespace to the original, reconciliation will fix this up. */ new_attr->ns = attr->ns; } } } return clone; } static xmlNodePtr dom_clone_helper(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr src_node, xmlDocPtr dst_doc, bool recursive) { xmlNodePtr outer_clone = dom_clone_container_helper(ns_mapper, src_node, dst_doc); if (!recursive || (src_node->type != XML_ELEMENT_NODE && src_node->type != XML_DOCUMENT_FRAG_NODE && src_node->type != XML_DOCUMENT_NODE && src_node->type != XML_HTML_DOCUMENT_NODE)) { return outer_clone; } /* Handle dtd separately, because it is linked twice and requires a special copy function. */ if (src_node->type == XML_DOCUMENT_NODE || src_node->type == XML_HTML_DOCUMENT_NODE) { dst_doc = (xmlDocPtr) outer_clone; xmlDtdPtr original_subset = ((xmlDocPtr) src_node)->intSubset; if (original_subset != NULL) { dst_doc->intSubset = xmlCopyDtd(((xmlDocPtr) src_node)->intSubset); if (UNEXPECTED(dst_doc->intSubset == NULL)) { xmlFreeNode(outer_clone); return NULL; } dst_doc->intSubset->parent = dst_doc; xmlSetTreeDoc((xmlNodePtr) dst_doc->intSubset, dst_doc); dst_doc->children = dst_doc->last = (xmlNodePtr) dst_doc->intSubset; } } xmlNodePtr cloned_parent = outer_clone; xmlNodePtr base = src_node; src_node = src_node->children; while (src_node != NULL) { ZEND_ASSERT(src_node != base); xmlNodePtr cloned; if (src_node->type == XML_ELEMENT_NODE) { cloned = dom_clone_container_helper(ns_mapper, src_node, dst_doc); } else if (src_node->type == XML_DTD_NODE) { /* Already handled. */ cloned = NULL; } else { cloned = xmlDocCopyNode(src_node, dst_doc, 1); } if (EXPECTED(cloned != NULL)) { if (cloned_parent->children == NULL) { cloned_parent->children = cloned; } else { cloned->prev = cloned_parent->last; cloned_parent->last->next = cloned; } cloned->parent = cloned_parent; cloned_parent->last = cloned; } if (src_node->type == XML_ELEMENT_NODE && src_node->children) { cloned_parent = cloned; src_node = src_node->children; } else if (src_node->next) { src_node = src_node->next; } else { /* Go upwards, until we find a parent node with a next sibling, or until we hit the base. */ do { src_node = src_node->parent; if (src_node == base) { return outer_clone; } cloned_parent = cloned_parent->parent; } while (src_node->next == NULL); src_node = src_node->next; } } return outer_clone; } xmlNodePtr dom_clone_node(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr node, xmlDocPtr doc, bool recursive) { if (node->type == XML_DTD_NODE) { /* The behaviour w.r.t. the internal subset is implementation-defined according to DOM 3. * This follows what e.g. Java and C# do: copy it regardless of the recursiveness. * Makes sense as the subset is not exactly a child in the normal sense. */ xmlDtdPtr dtd = xmlCopyDtd((xmlDtdPtr) node); xmlSetTreeDoc((xmlNodePtr) dtd, doc); return (xmlNodePtr) dtd; } if (ns_mapper != NULL) { xmlNodePtr clone = dom_clone_helper(ns_mapper, node, doc, recursive); /* Defensively set doc to NULL because we should not be using it after this point. * When cloning a document the new document will be clone->doc, not doc. */ doc = NULL; if (EXPECTED(clone != NULL) && clone->doc != node->doc) { /* We only need to reconcile the namespace when the document changes because the namespaces have to be * put into their respective namespace mapper. */ if (clone->type == XML_DOCUMENT_NODE || clone->type == XML_HTML_DOCUMENT_NODE || clone->type == XML_DOCUMENT_FRAG_NODE) { for (xmlNodePtr child = clone->children; child != NULL; child = child->next) { php_dom_libxml_reconcile_modern(ns_mapper, child); } } else { php_dom_libxml_reconcile_modern(ns_mapper, clone); } } return clone; } else { /* See http://www.xmlsoft.org/html/libxml-tree.html#xmlDocCopyNode for meaning of values */ int extended_recursive = recursive; if (!recursive && node->type == XML_ELEMENT_NODE) { extended_recursive = 2; } return xmlDocCopyNode(node, doc, extended_recursive); } } bool php_dom_has_child_of_type(xmlNodePtr node, xmlElementType type) { xmlNodePtr child = node->children; while (child != NULL) { if (child->type == type) { return true; } child = child->next; } return false; } bool php_dom_has_sibling_following_node(xmlNodePtr node, xmlElementType type) { xmlNodePtr next = node->next; while (next != NULL) { if (next->type == type) { return true; } next = next->next; } return false; } bool php_dom_has_sibling_preceding_node(xmlNodePtr node, xmlElementType type) { xmlNodePtr prev = node->prev; while (prev != NULL) { if (prev->type == type) { return true; } prev = prev->prev; } return false; } xmlAttrPtr php_dom_get_attribute_node(xmlNodePtr elem, const xmlChar *name, size_t name_len) { xmlChar *name_processed = BAD_CAST name; if (php_dom_ns_is_html_and_document_is_html(elem)) { char *lowercase_copy = zend_str_tolower_dup_ex((char *) name, name_len); if (lowercase_copy != NULL) { name_processed = BAD_CAST lowercase_copy; } } xmlAttrPtr ret = NULL; for (xmlAttrPtr attr = elem->properties; attr != NULL; attr = attr->next) { if (dom_match_qualified_name_according_to_spec(name_processed, (xmlNodePtr) attr)) { ret = attr; break; } } if (name_processed != name) { efree(name_processed); } return ret; } /* Workaround for a libxml2 bug on Windows: https://gitlab.gnome.org/GNOME/libxml2/-/issues/611. */ xmlChar *php_dom_libxml_fix_file_path(xmlChar *path) { if (strncmp((char *) path, "file:/", sizeof("file:/") - 1) == 0) { if (path[6] != '/' && path[6] != '\0' && path[7] != '/' && path[7] != '\0') { /* The path is file:/xx... where xx != "//", which is broken */ xmlChar *new_path = xmlStrdup(BAD_CAST "file:///"); if (UNEXPECTED(new_path == NULL)) { return path; } new_path = xmlStrcat(new_path, path + 6); xmlFree(path); return new_path; } } return path; } xmlDocPtr php_dom_create_html_doc(void) { #ifdef LIBXML_HTML_ENABLED xmlDocPtr lxml_doc = htmlNewDocNoDtD(NULL, NULL); if (EXPECTED(lxml_doc)) { lxml_doc->dict = xmlDictCreate(); } #else /* If HTML support is not enabled, then htmlNewDocNoDtD() is not available. * This code mimics the behaviour. */ xmlDocPtr lxml_doc = xmlNewDoc((const xmlChar *) "1.0"); if (EXPECTED(lxml_doc)) { lxml_doc->type = XML_HTML_DOCUMENT_NODE; lxml_doc->dict = xmlDictCreate(); } #endif return lxml_doc; } #endif /* HAVE_DOM */