Implement PHP-specific extensions to Dom (#14754)

See RFC: https://wiki.php.net/rfc/dom_additions_84
This commit is contained in:
Niels Dossche 2024-07-04 04:50:19 -07:00 committed by GitHub
parent 5b673e99fc
commit cf914f4184
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 807 additions and 1 deletions

View file

@ -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 */

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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)
{

View file

@ -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"
---

View file

@ -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"
---

View file

@ -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"

View file

@ -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

View file

@ -0,0 +1,38 @@
--TEST--
Element::$substitutedNodeValue
--EXTENSIONS--
dom
--FILE--
<?php
$dom = Dom\XMLDocument::createFromString('<root/>');
$dom->documentElement->substitutedNodeValue = "&#x31;";
var_dump($dom->documentElement->substitutedNodeValue);
var_dump($dom->documentElement->nodeValue); // Should always be NULL for elements
echo $dom->saveXML(), "\n";
$dom->documentElement->substitutedNodeValue = "&lt;&gt;";
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>&lt;&gt;</root>
string(0) ""
NULL
<?xml version="1.0" encoding="UTF-8"?>
<root></root>

View file

@ -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>

View 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

View file

@ -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/>