mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
Implement PHP-specific extensions to Dom (#14754)
See RFC: https://wiki.php.net/rfc/dom_additions_84
This commit is contained in:
parent
5b673e99fc
commit
cf914f4184
14 changed files with 807 additions and 1 deletions
|
@ -69,5 +69,6 @@ extern PHP_DOM_EXPORT zend_class_entry *dom_modern_xpath_class_entry;
|
|||
#endif
|
||||
extern PHP_DOM_EXPORT zend_class_entry *dom_namespace_node_class_entry;
|
||||
extern PHP_DOM_EXPORT zend_class_entry *dom_adjacent_position_class_entry;
|
||||
extern PHP_DOM_EXPORT zend_class_entry *dom_namespace_info_class_entry;
|
||||
|
||||
#endif /* DOM_CE_H */
|
||||
|
|
|
@ -86,6 +86,8 @@ zend_result dom_element_schema_type_info_read(dom_object *obj, zval *retval);
|
|||
zend_result dom_element_inner_html_read(dom_object *obj, zval *retval);
|
||||
zend_result dom_element_inner_html_write(dom_object *obj, zval *newval);
|
||||
zend_result dom_element_class_list_read(dom_object *obj, zval *retval);
|
||||
zend_result dom_modern_element_substituted_node_value_read(dom_object *obj, zval *retval);
|
||||
zend_result dom_modern_element_substituted_node_value_write(dom_object *obj, zval *newval);
|
||||
|
||||
/* entity properties */
|
||||
zend_result dom_entity_public_id_read(dom_object *obj, zval *retval);
|
||||
|
|
|
@ -1842,4 +1842,219 @@ PHP_METHOD(Dom_Element, closest)
|
|||
dom_element_closest(thisp, intern, return_value, selectors_str);
|
||||
}
|
||||
|
||||
zend_result dom_modern_element_substituted_node_value_read(dom_object *obj, zval *retval)
|
||||
{
|
||||
DOM_PROP_NODE(xmlNodePtr, nodep, obj);
|
||||
|
||||
xmlChar *content = xmlNodeGetContent(nodep);
|
||||
|
||||
if (UNEXPECTED(content == NULL)) {
|
||||
php_dom_throw_error(INVALID_STATE_ERR, true);
|
||||
return FAILURE;
|
||||
} else {
|
||||
ZVAL_STRING(retval, (const char *) content);
|
||||
xmlFree(content);
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
zend_result dom_modern_element_substituted_node_value_write(dom_object *obj, zval *newval)
|
||||
{
|
||||
DOM_PROP_NODE(xmlNodePtr, nodep, obj);
|
||||
|
||||
php_libxml_invalidate_node_list_cache(obj->document);
|
||||
dom_remove_all_children(nodep);
|
||||
xmlNodeSetContentLen(nodep, (xmlChar *) Z_STRVAL_P(newval), Z_STRLEN_P(newval));
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static void dom_element_get_in_scope_namespace_info(php_dom_libxml_ns_mapper *ns_mapper, HashTable *result, xmlNodePtr nodep, dom_object *intern)
|
||||
{
|
||||
HashTable prefix_to_ns_table;
|
||||
zend_hash_init(&prefix_to_ns_table, 0, NULL, NULL, false);
|
||||
zend_hash_real_init_mixed(&prefix_to_ns_table);
|
||||
|
||||
/* https://www.w3.org/TR/1999/REC-xpath-19991116/#namespace-nodes */
|
||||
for (const xmlNode *cur = nodep; cur != NULL; cur = cur->parent) {
|
||||
if (cur->type == XML_ELEMENT_NODE) {
|
||||
/* Find the last attribute */
|
||||
const xmlAttr *last = NULL;
|
||||
for (const xmlAttr *attr = cur->properties; attr != NULL; attr = attr->next) {
|
||||
last = attr;
|
||||
}
|
||||
|
||||
/* Reversed loop because the parent traversal is reversed as well,
|
||||
* this will keep the ordering consistent. */
|
||||
for (const xmlAttr *attr = last; attr != NULL; attr = attr->prev) {
|
||||
if (attr->ns != NULL && php_dom_ns_is_fast_ex(attr->ns, php_dom_ns_is_xmlns_magic_token)
|
||||
&& attr->children != NULL && attr->children->content != NULL) {
|
||||
const char *prefix = attr->ns->prefix == NULL ? NULL : (const char *) attr->name;
|
||||
const char *key = prefix == NULL ? "" : prefix;
|
||||
xmlNsPtr ns = php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ns_mapper, prefix, (const char *) attr->children->content);
|
||||
/* NULL is a valid value for the sentinel */
|
||||
zval zv;
|
||||
ZVAL_PTR(&zv, ns);
|
||||
zend_hash_str_add(&prefix_to_ns_table, key, strlen(key), &zv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xmlNsPtr ns;
|
||||
zend_string *prefix;
|
||||
ZEND_HASH_MAP_REVERSE_FOREACH_STR_KEY_PTR(&prefix_to_ns_table, prefix, ns) {
|
||||
if (ZSTR_LEN(prefix) == 0 && (ns == NULL || ns->href == NULL || *ns->href == '\0')) {
|
||||
/* Exception: "the value of the xmlns attribute for the nearest such element is non-empty" */
|
||||
continue;
|
||||
}
|
||||
|
||||
zval zv;
|
||||
object_init_ex(&zv, dom_namespace_info_class_entry);
|
||||
zend_object *obj = Z_OBJ(zv);
|
||||
|
||||
if (ZSTR_LEN(prefix) != 0) {
|
||||
ZVAL_STR_COPY(OBJ_PROP_NUM(obj, 0), prefix);
|
||||
} else {
|
||||
ZVAL_NULL(OBJ_PROP_NUM(obj, 0));
|
||||
}
|
||||
|
||||
if (ns != NULL && ns->href != NULL && *ns->href != '\0') {
|
||||
ZVAL_STRING(OBJ_PROP_NUM(obj, 1), (const char *) ns->href);
|
||||
} else {
|
||||
ZVAL_NULL(OBJ_PROP_NUM(obj, 1));
|
||||
}
|
||||
|
||||
php_dom_create_object(nodep, OBJ_PROP_NUM(obj, 2), intern);
|
||||
|
||||
zend_hash_next_index_insert_new(result, &zv);
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
|
||||
zend_hash_destroy(&prefix_to_ns_table);
|
||||
}
|
||||
|
||||
PHP_METHOD(Dom_Element, getInScopeNamespaces)
|
||||
{
|
||||
zval *id;
|
||||
xmlNode *nodep;
|
||||
dom_object *intern;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_NONE();
|
||||
|
||||
DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
|
||||
|
||||
php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
|
||||
|
||||
array_init(return_value);
|
||||
HashTable *result = Z_ARRVAL_P(return_value);
|
||||
|
||||
dom_element_get_in_scope_namespace_info(ns_mapper, result, nodep, intern);
|
||||
}
|
||||
|
||||
PHP_METHOD(Dom_Element, getDescendantNamespaces)
|
||||
{
|
||||
zval *id;
|
||||
xmlNode *nodep;
|
||||
dom_object *intern;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_NONE();
|
||||
|
||||
DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
|
||||
|
||||
php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
|
||||
|
||||
array_init(return_value);
|
||||
HashTable *result = Z_ARRVAL_P(return_value);
|
||||
|
||||
dom_element_get_in_scope_namespace_info(ns_mapper, result, nodep, intern);
|
||||
|
||||
xmlNodePtr cur = nodep->children;
|
||||
while (cur != NULL) {
|
||||
if (cur->type == XML_ELEMENT_NODE) {
|
||||
/* TODO: this could be more optimized by updating the same HashTable repeatedly
|
||||
* instead of recreating it on every node. */
|
||||
dom_element_get_in_scope_namespace_info(ns_mapper, result, cur, intern);
|
||||
}
|
||||
|
||||
cur = php_dom_next_in_tree_order(cur, nodep);
|
||||
}
|
||||
}
|
||||
|
||||
PHP_METHOD(Dom_Element, rename)
|
||||
{
|
||||
zend_string *namespace_uri, *qualified_name;
|
||||
ZEND_PARSE_PARAMETERS_START(2, 2)
|
||||
Z_PARAM_STR_OR_NULL(namespace_uri)
|
||||
Z_PARAM_STR(qualified_name)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
zval *id;
|
||||
dom_object *intern;
|
||||
xmlNodePtr nodep;
|
||||
DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
|
||||
|
||||
xmlChar *localname = NULL, *prefix = NULL;
|
||||
int errorcode = dom_validate_and_extract(namespace_uri, qualified_name, &localname, &prefix);
|
||||
if (UNEXPECTED(errorcode != 0)) {
|
||||
php_dom_throw_error(errorcode, /* strict */ true);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (nodep->type == XML_ATTRIBUTE_NODE) {
|
||||
/* Check for duplicate attributes. */
|
||||
xmlAttrPtr existing = xmlHasNsProp(nodep->parent, localname, namespace_uri && ZSTR_VAL(namespace_uri)[0] != '\0' ? BAD_CAST ZSTR_VAL(namespace_uri) : NULL);
|
||||
if (existing != NULL && existing != (xmlAttrPtr) nodep) {
|
||||
php_dom_throw_error_with_message(INVALID_MODIFICATION_ERR, "An attribute with the given name in the given namespace already exists", /* strict */ true);
|
||||
goto cleanup;
|
||||
}
|
||||
} else {
|
||||
ZEND_ASSERT(nodep->type == XML_ELEMENT_NODE);
|
||||
|
||||
/* Check for moving to or away from the HTML namespace. */
|
||||
bool is_currently_html_ns = php_dom_ns_is_fast(nodep, php_dom_ns_is_html_magic_token);
|
||||
bool will_be_html_ns = namespace_uri != NULL && zend_string_equals_literal(namespace_uri, DOM_XHTML_NS_URI);
|
||||
if (is_currently_html_ns != will_be_html_ns) {
|
||||
if (is_currently_html_ns) {
|
||||
php_dom_throw_error_with_message(
|
||||
INVALID_MODIFICATION_ERR,
|
||||
"It is not possible to move an element out of the HTML namespace because the HTML namespace is tied to the HTMLElement class",
|
||||
/* strict */ true
|
||||
);
|
||||
} else {
|
||||
php_dom_throw_error_with_message(
|
||||
INVALID_MODIFICATION_ERR,
|
||||
"It is not possible to move an element into the HTML namespace because the HTML namespace is tied to the HTMLElement class",
|
||||
/* strict */ true
|
||||
);
|
||||
}
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
php_libxml_invalidate_node_list_cache(intern->document);
|
||||
|
||||
php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
|
||||
|
||||
/* Update namespace uri + prefix by querying the namespace mapper */
|
||||
/* prefix can be NULL here, but that is taken care of by the called APIs. */
|
||||
nodep->ns = php_dom_libxml_ns_mapper_get_ns_raw_prefix_string(ns_mapper, prefix, xmlStrlen(prefix), namespace_uri);
|
||||
|
||||
/* Change the local name */
|
||||
if (xmlDictOwns(nodep->doc->dict, nodep->name) != 1) {
|
||||
xmlFree((xmlChar *) nodep->name);
|
||||
}
|
||||
const xmlChar *copy = xmlDictLookup(nodep->doc->dict, localname, -1);
|
||||
if (copy != NULL) {
|
||||
nodep->name = copy;
|
||||
} else {
|
||||
nodep->name = localname;
|
||||
localname = NULL;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
xmlFree(localname);
|
||||
xmlFree(prefix);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -89,6 +89,7 @@ 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;
|
||||
|
@ -840,6 +841,8 @@ PHP_MINIT_FUNCTION(dom)
|
|||
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;
|
||||
|
@ -1066,6 +1069,7 @@ PHP_MINIT_FUNCTION(dom)
|
|||
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);
|
||||
|
|
|
@ -1384,6 +1384,16 @@ namespace Dom
|
|||
public function matches(string $selectors): bool {}
|
||||
|
||||
public string $innerHTML;
|
||||
|
||||
public string $substitutedNodeValue;
|
||||
|
||||
/** @return list<NamespaceInfo> */
|
||||
public function getInScopeNamespaces(): array {}
|
||||
|
||||
/** @return list<NamespaceInfo> */
|
||||
public function getDescendantNamespaces(): array {}
|
||||
|
||||
public function rename(?string $namespaceURI, string $qualifiedName): void {}
|
||||
}
|
||||
|
||||
class HTMLElement extends Element
|
||||
|
@ -1410,6 +1420,9 @@ namespace Dom
|
|||
|
||||
/** @implementation-alias DOMAttr::isId */
|
||||
public function isId(): bool {}
|
||||
|
||||
/** @implementation-alias Dom\Element::rename */
|
||||
public function rename(?string $namespaceURI, string $qualifiedName): void {}
|
||||
}
|
||||
|
||||
class CharacterData extends Node implements ChildNode
|
||||
|
@ -1688,6 +1701,20 @@ namespace Dom
|
|||
public function getIterator(): \Iterator {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @not-serializable
|
||||
* @strict-properties
|
||||
*/
|
||||
readonly final class NamespaceInfo
|
||||
{
|
||||
public ?string $prefix;
|
||||
public ?string $namespaceURI;
|
||||
public Element $element;
|
||||
|
||||
/** @implementation-alias Dom\Node::__construct */
|
||||
private function __construct() {}
|
||||
}
|
||||
|
||||
#ifdef LIBXML_XPATH_ENABLED
|
||||
/** @not-serializable */
|
||||
final class XPath
|
||||
|
|
63
ext/dom/php_dom_arginfo.h
generated
63
ext/dom/php_dom_arginfo.h
generated
|
@ -1,5 +1,5 @@
|
|||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: 1faa01d0564052dda0dc41f36033a076b8b4c31c */
|
||||
* Stub hash: 1af73c3b63ebeb5e59948990892dcf6b627a1671 */
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0)
|
||||
|
@ -854,8 +854,19 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Dom_Element_matches, 0, 1,
|
|||
ZEND_ARG_TYPE_INFO(0, selectors, IS_STRING, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
#define arginfo_class_Dom_Element_getInScopeNamespaces arginfo_class_DOMNode___sleep
|
||||
|
||||
#define arginfo_class_Dom_Element_getDescendantNamespaces arginfo_class_DOMNode___sleep
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Dom_Element_rename, 0, 2, IS_VOID, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, namespaceURI, IS_STRING, 1)
|
||||
ZEND_ARG_TYPE_INFO(0, qualifiedName, IS_STRING, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
#define arginfo_class_Dom_Attr_isId arginfo_class_Dom_Node_hasChildNodes
|
||||
|
||||
#define arginfo_class_Dom_Attr_rename arginfo_class_Dom_Element_rename
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Dom_CharacterData_substringData, 0, 2, IS_STRING, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, count, IS_LONG, 0)
|
||||
|
@ -1111,6 +1122,8 @@ ZEND_END_ARG_INFO()
|
|||
|
||||
#define arginfo_class_Dom_TokenList_getIterator arginfo_class_DOMNodeList_getIterator
|
||||
|
||||
#define arginfo_class_Dom_NamespaceInfo___construct arginfo_class_DOMDocumentFragment___construct
|
||||
|
||||
#if defined(LIBXML_XPATH_ENABLED)
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Dom_XPath___construct, 0, 0, 1)
|
||||
ZEND_ARG_OBJ_INFO(0, document, Dom\\Document, 0)
|
||||
|
@ -1334,6 +1347,9 @@ ZEND_METHOD(Dom_Element, querySelector);
|
|||
ZEND_METHOD(Dom_Element, querySelectorAll);
|
||||
ZEND_METHOD(Dom_Element, closest);
|
||||
ZEND_METHOD(Dom_Element, matches);
|
||||
ZEND_METHOD(Dom_Element, getInScopeNamespaces);
|
||||
ZEND_METHOD(Dom_Element, getDescendantNamespaces);
|
||||
ZEND_METHOD(Dom_Element, rename);
|
||||
ZEND_METHOD(Dom_CharacterData, appendData);
|
||||
ZEND_METHOD(Dom_CharacterData, insertData);
|
||||
ZEND_METHOD(Dom_CharacterData, deleteData);
|
||||
|
@ -1748,6 +1764,9 @@ static const zend_function_entry class_Dom_Element_methods[] = {
|
|||
ZEND_ME(Dom_Element, querySelectorAll, arginfo_class_Dom_Element_querySelectorAll, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Dom_Element, closest, arginfo_class_Dom_Element_closest, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Dom_Element, matches, arginfo_class_Dom_Element_matches, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Dom_Element, getInScopeNamespaces, arginfo_class_Dom_Element_getInScopeNamespaces, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Dom_Element, getDescendantNamespaces, arginfo_class_Dom_Element_getDescendantNamespaces, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Dom_Element, rename, arginfo_class_Dom_Element_rename, ZEND_ACC_PUBLIC)
|
||||
ZEND_FE_END
|
||||
};
|
||||
|
||||
|
@ -1757,6 +1776,7 @@ static const zend_function_entry class_Dom_HTMLElement_methods[] = {
|
|||
|
||||
static const zend_function_entry class_Dom_Attr_methods[] = {
|
||||
ZEND_RAW_FENTRY("isId", zim_DOMAttr_isId, arginfo_class_Dom_Attr_isId, ZEND_ACC_PUBLIC, NULL, NULL)
|
||||
ZEND_RAW_FENTRY("rename", zim_Dom_Element_rename, arginfo_class_Dom_Attr_rename, ZEND_ACC_PUBLIC, NULL, NULL)
|
||||
ZEND_FE_END
|
||||
};
|
||||
|
||||
|
@ -1894,6 +1914,11 @@ static const zend_function_entry class_Dom_TokenList_methods[] = {
|
|||
ZEND_FE_END
|
||||
};
|
||||
|
||||
static const zend_function_entry class_Dom_NamespaceInfo_methods[] = {
|
||||
ZEND_RAW_FENTRY("__construct", zim_Dom_Node___construct, arginfo_class_Dom_NamespaceInfo___construct, ZEND_ACC_PRIVATE, NULL, NULL)
|
||||
ZEND_FE_END
|
||||
};
|
||||
|
||||
#if defined(LIBXML_XPATH_ENABLED)
|
||||
static const zend_function_entry class_Dom_XPath_methods[] = {
|
||||
ZEND_ME(Dom_XPath, __construct, arginfo_class_Dom_XPath___construct, ZEND_ACC_PUBLIC)
|
||||
|
@ -3189,6 +3214,12 @@ static zend_class_entry *register_class_Dom_Element(zend_class_entry *class_entr
|
|||
zend_declare_typed_property(class_entry, property_innerHTML_name, &property_innerHTML_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
|
||||
zend_string_release(property_innerHTML_name);
|
||||
|
||||
zval property_substitutedNodeValue_default_value;
|
||||
ZVAL_UNDEF(&property_substitutedNodeValue_default_value);
|
||||
zend_string *property_substitutedNodeValue_name = zend_string_init("substitutedNodeValue", sizeof("substitutedNodeValue") - 1, 1);
|
||||
zend_declare_typed_property(class_entry, property_substitutedNodeValue_name, &property_substitutedNodeValue_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
|
||||
zend_string_release(property_substitutedNodeValue_name);
|
||||
|
||||
return class_entry;
|
||||
}
|
||||
|
||||
|
@ -3657,6 +3688,36 @@ static zend_class_entry *register_class_Dom_TokenList(zend_class_entry *class_en
|
|||
return class_entry;
|
||||
}
|
||||
|
||||
static zend_class_entry *register_class_Dom_NamespaceInfo(void)
|
||||
{
|
||||
zend_class_entry ce, *class_entry;
|
||||
|
||||
INIT_NS_CLASS_ENTRY(ce, "Dom", "NamespaceInfo", class_Dom_NamespaceInfo_methods);
|
||||
class_entry = zend_register_internal_class_ex(&ce, NULL);
|
||||
class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE|ZEND_ACC_READONLY_CLASS;
|
||||
|
||||
zval property_prefix_default_value;
|
||||
ZVAL_UNDEF(&property_prefix_default_value);
|
||||
zend_string *property_prefix_name = zend_string_init("prefix", sizeof("prefix") - 1, 1);
|
||||
zend_declare_typed_property(class_entry, property_prefix_name, &property_prefix_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL));
|
||||
zend_string_release(property_prefix_name);
|
||||
|
||||
zval property_namespaceURI_default_value;
|
||||
ZVAL_UNDEF(&property_namespaceURI_default_value);
|
||||
zend_string *property_namespaceURI_name = zend_string_init("namespaceURI", sizeof("namespaceURI") - 1, 1);
|
||||
zend_declare_typed_property(class_entry, property_namespaceURI_name, &property_namespaceURI_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL));
|
||||
zend_string_release(property_namespaceURI_name);
|
||||
|
||||
zval property_element_default_value;
|
||||
ZVAL_UNDEF(&property_element_default_value);
|
||||
zend_string *property_element_name = zend_string_init("element", sizeof("element") - 1, 1);
|
||||
zend_string *property_element_class_Dom_Element = zend_string_init("Dom\\Element", sizeof("Dom\\Element")-1, 1);
|
||||
zend_declare_typed_property(class_entry, property_element_name, &property_element_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_element_class_Dom_Element, 0, 0));
|
||||
zend_string_release(property_element_name);
|
||||
|
||||
return class_entry;
|
||||
}
|
||||
|
||||
#if defined(LIBXML_XPATH_ENABLED)
|
||||
static zend_class_entry *register_class_Dom_XPath(void)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
--TEST--
|
||||
DOM\Element::getDescendantNamespaces()
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function dump($dom, $name) {
|
||||
echo "\n=== $name ===\n";
|
||||
$list = $dom->getElementsByTagName($name)[0]->getDescendantNamespaces();
|
||||
foreach ($list as $entry) {
|
||||
echo "prefix: ";
|
||||
var_dump($entry->prefix);
|
||||
echo "namespaceURI: ";
|
||||
var_dump($entry->namespaceURI);
|
||||
echo "element->nodeName: ";
|
||||
var_dump($entry->element->nodeName);
|
||||
echo "---\n";
|
||||
}
|
||||
}
|
||||
|
||||
$dom = DOM\XMLDocument::createFromString(<<<XML
|
||||
<root xmlns="urn:a">
|
||||
<child xmlns="">
|
||||
<c:child xmlns:c="urn:c"/>
|
||||
</child>
|
||||
<b:sibling xmlns:b="urn:b" xmlns:d="urn:d" d:foo="bar">
|
||||
<d:child xmlns:d="urn:d2"/>
|
||||
</b:sibling>
|
||||
</root>
|
||||
XML);
|
||||
|
||||
dump($dom, 'c:child');
|
||||
dump($dom, 'child');
|
||||
dump($dom, 'b:sibling');
|
||||
dump($dom, 'd:child');
|
||||
dump($dom, 'root');
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
=== c:child ===
|
||||
prefix: string(1) "c"
|
||||
namespaceURI: string(5) "urn:c"
|
||||
element->nodeName: string(7) "c:child"
|
||||
---
|
||||
|
||||
=== child ===
|
||||
prefix: string(1) "c"
|
||||
namespaceURI: string(5) "urn:c"
|
||||
element->nodeName: string(7) "c:child"
|
||||
---
|
||||
|
||||
=== b:sibling ===
|
||||
prefix: NULL
|
||||
namespaceURI: string(5) "urn:a"
|
||||
element->nodeName: string(9) "b:sibling"
|
||||
---
|
||||
prefix: string(1) "b"
|
||||
namespaceURI: string(5) "urn:b"
|
||||
element->nodeName: string(9) "b:sibling"
|
||||
---
|
||||
prefix: string(1) "d"
|
||||
namespaceURI: string(5) "urn:d"
|
||||
element->nodeName: string(9) "b:sibling"
|
||||
---
|
||||
prefix: NULL
|
||||
namespaceURI: string(5) "urn:a"
|
||||
element->nodeName: string(7) "d:child"
|
||||
---
|
||||
prefix: string(1) "b"
|
||||
namespaceURI: string(5) "urn:b"
|
||||
element->nodeName: string(7) "d:child"
|
||||
---
|
||||
prefix: string(1) "d"
|
||||
namespaceURI: string(6) "urn:d2"
|
||||
element->nodeName: string(7) "d:child"
|
||||
---
|
||||
|
||||
=== d:child ===
|
||||
prefix: NULL
|
||||
namespaceURI: string(5) "urn:a"
|
||||
element->nodeName: string(7) "d:child"
|
||||
---
|
||||
prefix: string(1) "b"
|
||||
namespaceURI: string(5) "urn:b"
|
||||
element->nodeName: string(7) "d:child"
|
||||
---
|
||||
prefix: string(1) "d"
|
||||
namespaceURI: string(6) "urn:d2"
|
||||
element->nodeName: string(7) "d:child"
|
||||
---
|
||||
|
||||
=== root ===
|
||||
prefix: NULL
|
||||
namespaceURI: string(5) "urn:a"
|
||||
element->nodeName: string(4) "root"
|
||||
---
|
||||
prefix: string(1) "c"
|
||||
namespaceURI: string(5) "urn:c"
|
||||
element->nodeName: string(7) "c:child"
|
||||
---
|
||||
prefix: NULL
|
||||
namespaceURI: string(5) "urn:a"
|
||||
element->nodeName: string(9) "b:sibling"
|
||||
---
|
||||
prefix: string(1) "b"
|
||||
namespaceURI: string(5) "urn:b"
|
||||
element->nodeName: string(9) "b:sibling"
|
||||
---
|
||||
prefix: string(1) "d"
|
||||
namespaceURI: string(5) "urn:d"
|
||||
element->nodeName: string(9) "b:sibling"
|
||||
---
|
||||
prefix: NULL
|
||||
namespaceURI: string(5) "urn:a"
|
||||
element->nodeName: string(7) "d:child"
|
||||
---
|
||||
prefix: string(1) "b"
|
||||
namespaceURI: string(5) "urn:b"
|
||||
element->nodeName: string(7) "d:child"
|
||||
---
|
||||
prefix: string(1) "d"
|
||||
namespaceURI: string(6) "urn:d2"
|
||||
element->nodeName: string(7) "d:child"
|
||||
---
|
|
@ -0,0 +1,81 @@
|
|||
--TEST--
|
||||
Dom\Element::getInScopeNamespaces()
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function dump($dom, $name) {
|
||||
echo "\n=== $name ===\n";
|
||||
$list = $dom->getElementsByTagName($name)[0]->getInScopeNamespaces();
|
||||
foreach ($list as $entry) {
|
||||
echo "prefix: ";
|
||||
var_dump($entry->prefix);
|
||||
echo "namespaceURI: ";
|
||||
var_dump($entry->namespaceURI);
|
||||
echo "element->nodeName: ";
|
||||
var_dump($entry->element->nodeName);
|
||||
echo "---\n";
|
||||
}
|
||||
}
|
||||
|
||||
$dom = Dom\XMLDocument::createFromString(<<<XML
|
||||
<root xmlns="urn:a">
|
||||
<child xmlns="">
|
||||
<c:child xmlns:c="urn:c"/>
|
||||
</child>
|
||||
<b:sibling xmlns:b="urn:b" xmlns:d="urn:d" d:foo="bar">
|
||||
<d:child xmlns:d="urn:d2"/>
|
||||
</b:sibling>
|
||||
</root>
|
||||
XML);
|
||||
|
||||
dump($dom, 'c:child');
|
||||
dump($dom, 'child');
|
||||
dump($dom, 'b:sibling');
|
||||
dump($dom, 'd:child');
|
||||
dump($dom, 'root');
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
=== c:child ===
|
||||
prefix: string(1) "c"
|
||||
namespaceURI: string(5) "urn:c"
|
||||
element->nodeName: string(7) "c:child"
|
||||
---
|
||||
|
||||
=== child ===
|
||||
|
||||
=== b:sibling ===
|
||||
prefix: NULL
|
||||
namespaceURI: string(5) "urn:a"
|
||||
element->nodeName: string(9) "b:sibling"
|
||||
---
|
||||
prefix: string(1) "b"
|
||||
namespaceURI: string(5) "urn:b"
|
||||
element->nodeName: string(9) "b:sibling"
|
||||
---
|
||||
prefix: string(1) "d"
|
||||
namespaceURI: string(5) "urn:d"
|
||||
element->nodeName: string(9) "b:sibling"
|
||||
---
|
||||
|
||||
=== d:child ===
|
||||
prefix: NULL
|
||||
namespaceURI: string(5) "urn:a"
|
||||
element->nodeName: string(7) "d:child"
|
||||
---
|
||||
prefix: string(1) "b"
|
||||
namespaceURI: string(5) "urn:b"
|
||||
element->nodeName: string(7) "d:child"
|
||||
---
|
||||
prefix: string(1) "d"
|
||||
namespaceURI: string(6) "urn:d2"
|
||||
element->nodeName: string(7) "d:child"
|
||||
---
|
||||
|
||||
=== root ===
|
||||
prefix: NULL
|
||||
namespaceURI: string(5) "urn:a"
|
||||
element->nodeName: string(4) "root"
|
||||
---
|
|
@ -0,0 +1,25 @@
|
|||
--TEST--
|
||||
Element renaming interaction with the HTML namespace 01
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$dom = DOM\XMLDocument::createEmpty();
|
||||
$el = $dom->createElementNS("http://www.w3.org/1999/xhtml", "foo:bar");
|
||||
$el->rename("http://www.w3.org/1999/xhtml", "foo:baz");
|
||||
var_dump($el->nodeName, $el->namespaceURI, $el->prefix);
|
||||
|
||||
// Very subtly *not* the HTML namespace!
|
||||
$el = $dom->createElementNS("http://www.w3.org/1999/xhtml/", "foo:bar");
|
||||
$el->rename("urn:x", "foo:baz");
|
||||
var_dump($el->nodeName, $el->namespaceURI, $el->prefix);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
string(7) "foo:baz"
|
||||
string(28) "http://www.w3.org/1999/xhtml"
|
||||
string(3) "foo"
|
||||
string(7) "foo:baz"
|
||||
string(5) "urn:x"
|
||||
string(3) "foo"
|
|
@ -0,0 +1,25 @@
|
|||
--TEST--
|
||||
Element renaming interaction with the HTML namespace 02
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$dom = DOM\XMLDocument::createEmpty();
|
||||
$el = $dom->createElementNS("http://www.w3.org/1999/xhtml", "foo:bar");
|
||||
try {
|
||||
$el->rename("urn:a", "foo:baz");
|
||||
} catch (DOMException $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
$el = $dom->createElementNS("urn:a", "foo:bar");
|
||||
try {
|
||||
$el->rename("http://www.w3.org/1999/xhtml", "foo:baz");
|
||||
} catch (DOMException $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
It is not possible to move an element out of the HTML namespace because the HTML namespace is tied to the HTMLElement class
|
||||
It is not possible to move an element into the HTML namespace because the HTML namespace is tied to the HTMLElement class
|
|
@ -0,0 +1,38 @@
|
|||
--TEST--
|
||||
Element::$substitutedNodeValue
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$dom = Dom\XMLDocument::createFromString('<root/>');
|
||||
|
||||
$dom->documentElement->substitutedNodeValue = "1";
|
||||
var_dump($dom->documentElement->substitutedNodeValue);
|
||||
var_dump($dom->documentElement->nodeValue); // Should always be NULL for elements
|
||||
echo $dom->saveXML(), "\n";
|
||||
|
||||
$dom->documentElement->substitutedNodeValue = "<>";
|
||||
var_dump($dom->documentElement->substitutedNodeValue);
|
||||
var_dump($dom->documentElement->nodeValue); // Should always be NULL for elements
|
||||
echo $dom->saveXML(), "\n";
|
||||
|
||||
$dom->documentElement->substitutedNodeValue = "";
|
||||
var_dump($dom->documentElement->substitutedNodeValue);
|
||||
var_dump($dom->documentElement->nodeValue); // Should always be NULL for elements
|
||||
echo $dom->saveXML(), "\n";
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
string(1) "1"
|
||||
NULL
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>1</root>
|
||||
string(2) "<>"
|
||||
NULL
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root><></root>
|
||||
string(0) ""
|
||||
NULL
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root></root>
|
|
@ -0,0 +1,75 @@
|
|||
--TEST--
|
||||
Renaming an attribute node to a name that already exists
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$dom = DOM\XMLDocument::createFromString(<<<XML
|
||||
<!DOCTYPE root [
|
||||
<!ELEMENT implied-attribute ANY>
|
||||
<!ATTLIST implied-attribute hello CDATA #FIXED "world">
|
||||
]>
|
||||
<root a="b" c="d" xmlns:ns1="urn:a" ns1:foo="bar">
|
||||
<implied-attribute my-attr="x"/>
|
||||
<implied-attribute my-attr="x"/>
|
||||
</root>
|
||||
XML, LIBXML_DTDATTR);
|
||||
|
||||
$root = $dom->documentElement;
|
||||
try {
|
||||
$root->attributes[0]->rename(NULL, 'c');
|
||||
} catch (DOMException $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$root->attributes[0]->rename(NULL, 'c');
|
||||
} catch (DOMException $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$root->attributes[1]->rename(NULL, 'a');
|
||||
} catch (DOMException $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$root->attributes[1]->rename('urn:a', 'foo');
|
||||
} catch (DOMException $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$root->attributes[3]->rename('', 'a');
|
||||
} catch (DOMException $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$root->firstElementChild->attributes[0]->rename(NULL, 'hello');
|
||||
} catch (DOMException $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$root->firstElementChild->attributes[1]->rename(NULL, 'my-attr');
|
||||
} catch (DOMException $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
// This is here to validate that nothing actually changed
|
||||
echo $dom->saveXML();
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
An attribute with the given name in the given namespace already exists
|
||||
An attribute with the given name in the given namespace already exists
|
||||
An attribute with the given name in the given namespace already exists
|
||||
An attribute with the given name in the given namespace already exists
|
||||
An attribute with the given name in the given namespace already exists
|
||||
An attribute with the given name in the given namespace already exists
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE root [
|
||||
<!ELEMENT implied-attribute ANY>
|
||||
<!ATTLIST implied-attribute hello CDATA #FIXED "world">
|
||||
]>
|
||||
<root xmlns:ns1="urn:a" a="b" c="d" ns1:foo="bar">
|
||||
<implied-attribute my-attr="x" hello="world"/>
|
||||
<implied-attribute my-attr="x" hello="world"/>
|
||||
</root>
|
98
ext/dom/tests/modern/extensions/node_renaming.phpt
Normal file
98
ext/dom/tests/modern/extensions/node_renaming.phpt
Normal file
|
@ -0,0 +1,98 @@
|
|||
--TEST--
|
||||
Node renaming
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$dom = Dom\XMLDocument::createFromString(<<<XML
|
||||
<root xmlns:a="urn:a">
|
||||
<a:child attrib="value"/>
|
||||
</root>
|
||||
XML);
|
||||
|
||||
function test($target, $namespaceURI, $qualifiedName) {
|
||||
$namespaceURIPretty = json_encode($namespaceURI);
|
||||
$qualifiedNamePretty = json_encode($qualifiedName);
|
||||
echo "--- rename to $namespaceURIPretty $qualifiedNamePretty ---\n";
|
||||
$target->rename($namespaceURI, $qualifiedName);
|
||||
echo $target->ownerDocument->saveXML(), "\n";
|
||||
var_dump($target->namespaceURI, $target->prefix);
|
||||
}
|
||||
|
||||
echo "=== Element test ===\n";
|
||||
|
||||
test($dom->documentElement, "urn:x", "x:foo");
|
||||
test($dom->documentElement, "urn:x", "a:foo");
|
||||
test($dom->documentElement, "", "foo");
|
||||
test($dom->documentElement, null, "bar");
|
||||
|
||||
echo "=== Attribute test ===\n";
|
||||
|
||||
$attribute = $dom->documentElement->firstElementChild->attributes[0];
|
||||
|
||||
test($attribute, "urn:x", "x:foo");
|
||||
test($attribute, "urn:x", "a:foo");
|
||||
test($attribute, "", "foo");
|
||||
test($attribute, null, "bar");
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
=== Element test ===
|
||||
--- rename to "urn:x" "x:foo" ---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<x:foo xmlns:x="urn:x" xmlns:a="urn:a">
|
||||
<a:child attrib="value"/>
|
||||
</x:foo>
|
||||
string(5) "urn:x"
|
||||
string(1) "x"
|
||||
--- rename to "urn:x" "a:foo" ---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ns1:foo xmlns:ns1="urn:x" xmlns:a="urn:a">
|
||||
<a:child attrib="value"/>
|
||||
</ns1:foo>
|
||||
string(5) "urn:x"
|
||||
string(1) "a"
|
||||
--- rename to "" "foo" ---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<foo xmlns:a="urn:a">
|
||||
<a:child attrib="value"/>
|
||||
</foo>
|
||||
NULL
|
||||
NULL
|
||||
--- rename to null "bar" ---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bar xmlns:a="urn:a">
|
||||
<a:child attrib="value"/>
|
||||
</bar>
|
||||
NULL
|
||||
NULL
|
||||
=== Attribute test ===
|
||||
--- rename to "urn:x" "x:foo" ---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bar xmlns:a="urn:a">
|
||||
<a:child xmlns:x="urn:x" x:foo="value"/>
|
||||
</bar>
|
||||
string(5) "urn:x"
|
||||
string(1) "x"
|
||||
--- rename to "urn:x" "a:foo" ---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bar xmlns:a="urn:a">
|
||||
<a:child xmlns:a="urn:x" a:foo="value"/>
|
||||
</bar>
|
||||
string(5) "urn:x"
|
||||
string(1) "a"
|
||||
--- rename to "" "foo" ---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bar xmlns:a="urn:a">
|
||||
<a:child foo="value"/>
|
||||
</bar>
|
||||
NULL
|
||||
NULL
|
||||
--- rename to null "bar" ---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bar xmlns:a="urn:a">
|
||||
<a:child bar="value"/>
|
||||
</bar>
|
||||
NULL
|
||||
NULL
|
|
@ -0,0 +1,29 @@
|
|||
--TEST--
|
||||
Node renaming validation errors
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$dom = Dom\XMLDocument::createFromString('<root/>');
|
||||
|
||||
try {
|
||||
$dom->documentElement->rename('', 'a:b');
|
||||
} catch (DOMException $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$dom->documentElement->rename('urn:a', 'a:b:c');
|
||||
} catch (DOMException $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
echo $dom->saveXML();
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Namespace Error
|
||||
Invalid Character Error
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root/>
|
Loading…
Add table
Add a link
Reference in a new issue