1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Merge branch 'PHP-8.2'

* PHP-8.2:
  Fix bug #77686: Removed elements are still returned by getElementById
  Fix bug #81642: DOMChildNode::replaceWith() bug when replacing a node with itself
  Fix bug #67440: append_node of a DOMDocumentFragment does not reconcile namespaces
This commit is contained in:
Niels Dossche
2023-06-04 16:34:42 +02:00
9 changed files with 444 additions and 75 deletions

View File

@@ -1005,6 +1005,19 @@ PHP_METHOD(DOMDocument, getElementsByTagNameNS)
}
/* }}} end dom_document_get_elements_by_tag_name_ns */
static bool php_dom_is_node_attached(const xmlNode *node)
{
ZEND_ASSERT(node != NULL);
node = node->parent;
while (node != NULL) {
if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) {
return true;
}
node = node->parent;
}
return false;
}
/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-getElBId
Since: DOM Level 2
*/
@@ -1027,7 +1040,13 @@ PHP_METHOD(DOMDocument, getElementById)
attrp = xmlGetID(docp, (xmlChar *) idname);
if (attrp && attrp->parent) {
/* From the moment an ID is created, libxml2's behaviour is to cache that element, even
* if that element is not yet attached to the document. Similarly, only upon destruction of
* the element the ID is actually removed by libxml2. Since libxml2 has such behaviour deeply
* ingrained in the library, and uses the cache for various purposes, it seems like a bad
* idea and lost cause to fight it. Instead, we'll simply walk the tree upwards to check
* if the node is attached to the document. */
if (attrp && attrp->parent && php_dom_is_node_attached(attrp->parent)) {
DOM_RET_OBJ((xmlNodePtr) attrp->parent, &ret, intern);
} else {
RETVAL_NULL();

View File

@@ -1229,7 +1229,7 @@ PHP_METHOD(DOMElement, prepend)
}
/* }}} end DOMElement::prepend */
/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-prepend
/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
Since: DOM Living Standard (DOM4)
*/
PHP_METHOD(DOMElement, replaceWith)
@@ -1246,8 +1246,7 @@ PHP_METHOD(DOMElement, replaceWith)
id = ZEND_THIS;
DOM_GET_OBJ(context, id, xmlNodePtr, intern);
dom_parent_node_after(intern, args, argc);
dom_child_node_remove(intern);
dom_child_replace_with(intern, args, argc);
}
/* }}} end DOMElement::prepend */

View File

@@ -949,12 +949,20 @@ PHP_METHOD(DOMNode, insertBefore)
return;
}
}
} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
new_child = _php_dom_insert_fragment(parentp, refp->prev, refp, child, intern, childobj);
}
if (new_child == NULL) {
new_child = xmlAddPrevSibling(refp, child);
if (UNEXPECTED(NULL == new_child)) {
goto cannot_add;
}
} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
xmlNodePtr last = child->last;
new_child = _php_dom_insert_fragment(parentp, refp->prev, refp, child, intern, childobj);
dom_reconcile_ns_list(parentp->doc, new_child, last);
} else {
new_child = xmlAddPrevSibling(refp, child);
if (UNEXPECTED(NULL == new_child)) {
goto cannot_add;
}
dom_reconcile_ns(parentp->doc, new_child);
}
} else {
if (child->parent != NULL){
@@ -991,23 +999,28 @@ PHP_METHOD(DOMNode, insertBefore)
return;
}
}
} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
new_child = _php_dom_insert_fragment(parentp, parentp->last, NULL, child, intern, childobj);
}
if (new_child == NULL) {
new_child = xmlAddChild(parentp, child);
if (UNEXPECTED(NULL == new_child)) {
goto cannot_add;
}
} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
xmlNodePtr last = child->last;
new_child = _php_dom_insert_fragment(parentp, parentp->last, NULL, child, intern, childobj);
dom_reconcile_ns_list(parentp->doc, new_child, last);
} else {
new_child = xmlAddChild(parentp, child);
if (UNEXPECTED(NULL == new_child)) {
goto cannot_add;
}
dom_reconcile_ns(parentp->doc, new_child);
}
}
if (NULL == new_child) {
zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode");
RETURN_THROWS();
}
dom_reconcile_ns(parentp->doc, new_child);
DOM_RET_OBJ(new_child, &ret, intern);
return;
cannot_add:
zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode");
RETURN_THROWS();
}
/* }}} end dom_node_insert_before */
@@ -1072,9 +1085,10 @@ PHP_METHOD(DOMNode, replaceChild)
xmlUnlinkNode(oldchild);
xmlNodePtr last = newchild->last;
newchild = _php_dom_insert_fragment(nodep, prevsib, nextsib, newchild, intern, newchildobj);
if (newchild) {
dom_reconcile_ns(nodep->doc, newchild);
dom_reconcile_ns_list(nodep->doc, newchild, last);
}
} else if (oldchild != newchild) {
xmlDtdPtr intSubset = xmlGetIntSubset(nodep->doc);
@@ -1223,24 +1237,30 @@ PHP_METHOD(DOMNode, appendChild)
php_libxml_node_free_resource((xmlNodePtr) lastattr);
}
}
} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
new_child = _php_dom_insert_fragment(nodep, nodep->last, NULL, child, intern, childobj);
}
if (new_child == NULL) {
new_child = xmlAddChild(nodep, child);
if (new_child == NULL) {
// TODO Convert to Error?
php_error_docref(NULL, E_WARNING, "Couldn't append node");
RETURN_FALSE;
if (UNEXPECTED(new_child == NULL)) {
goto cannot_add;
}
} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
xmlNodePtr last = child->last;
new_child = _php_dom_insert_fragment(nodep, nodep->last, NULL, child, intern, childobj);
dom_reconcile_ns_list(nodep->doc, new_child, last);
} else {
new_child = xmlAddChild(nodep, child);
if (UNEXPECTED(new_child == NULL)) {
goto cannot_add;
}
dom_reconcile_ns(nodep->doc, new_child);
}
dom_reconcile_ns(nodep->doc, new_child);
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
DOM_RET_OBJ(new_child, &ret, intern);
return;
cannot_add:
// TODO Convert to Error?
php_error_docref(NULL, E_WARNING, "Couldn't append node");
RETURN_FALSE;
}
/* }}} end dom_node_append_child */

View File

@@ -298,13 +298,14 @@ void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc)
parentNode->children = newchild;
}
parentNode->last = fragment->last;
xmlNodePtr last = fragment->last;
parentNode->last = last;
newchild->prev = prevsib;
dom_fragment_assign_parent_node(parentNode, fragment);
dom_reconcile_ns(parentNode->doc, newchild);
dom_reconcile_ns_list(parentNode->doc, newchild, last);
}
xmlFree(fragment);
@@ -337,13 +338,14 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc)
nextsib = parentNode->children;
if (newchild) {
xmlNodePtr last = fragment->last;
parentNode->children = newchild;
fragment->last->next = nextsib;
nextsib->prev = fragment->last;
nextsib->prev = last;
dom_fragment_assign_parent_node(parentNode, fragment);
dom_reconcile_ns(parentNode->doc, newchild);
dom_reconcile_ns_list(parentNode->doc, newchild, last);
}
xmlFree(fragment);
@@ -418,11 +420,13 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
newchild = fragment->children;
if (newchild) {
xmlNodePtr last = fragment->last;
/* Step 5: place fragment into the parent before viable_next_sibling */
dom_pre_insert(viable_next_sibling, parentNode, newchild, fragment);
dom_fragment_assign_parent_node(parentNode, fragment);
dom_reconcile_ns(doc, newchild);
dom_reconcile_ns_list(doc, newchild, last);
}
xmlFree(fragment);
@@ -469,6 +473,8 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
newchild = fragment->children;
if (newchild) {
xmlNodePtr last = fragment->last;
/* Step 5: if viable_previous_sibling is null, set it to the parent's first child, otherwise viable_previous_sibling's next sibling */
if (!viable_previous_sibling) {
viable_previous_sibling = parentNode->children;
@@ -479,12 +485,38 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
dom_pre_insert(viable_previous_sibling, parentNode, newchild, fragment);
dom_fragment_assign_parent_node(parentNode, fragment);
dom_reconcile_ns(doc, newchild);
dom_reconcile_ns_list(doc, newchild, last);
}
xmlFree(fragment);
}
static zend_result dom_child_removal_preconditions(const xmlNodePtr child, int stricterror)
{
if (dom_node_is_read_only(child) == SUCCESS ||
(child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
return FAILURE;
}
if (!child->parent) {
php_dom_throw_error(NOT_FOUND_ERR, stricterror);
return FAILURE;
}
if (dom_node_children_valid(child->parent) == FAILURE) {
return FAILURE;
}
xmlNodePtr children = child->parent->children;
if (!children) {
php_dom_throw_error(NOT_FOUND_ERR, stricterror);
return FAILURE;
}
return SUCCESS;
}
void dom_child_node_remove(dom_object *context)
{
xmlNode *child = dom_object_get_node(context);
@@ -493,26 +525,11 @@ void dom_child_node_remove(dom_object *context)
stricterror = dom_get_strict_error(context->document);
if (dom_node_is_read_only(child) == SUCCESS ||
(child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
return;
}
if (!child->parent) {
php_dom_throw_error(NOT_FOUND_ERR, stricterror);
return;
}
if (dom_node_children_valid(child->parent) == FAILURE) {
if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) {
return;
}
children = child->parent->children;
if (!children) {
php_dom_throw_error(NOT_FOUND_ERR, stricterror);
return;
}
php_libxml_invalidate_node_list_cache_from_doc(context->document->ptr);
@@ -527,4 +544,41 @@ void dom_child_node_remove(dom_object *context)
php_dom_throw_error(NOT_FOUND_ERR, stricterror);
}
void dom_child_replace_with(dom_object *context, zval *nodes, int nodesc)
{
xmlNodePtr child = dom_object_get_node(context);
xmlNodePtr parentNode = child->parent;
int stricterror = dom_get_strict_error(context->document);
if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) {
return;
}
xmlNodePtr insertion_point = child->next;
xmlNodePtr fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
if (UNEXPECTED(fragment == NULL)) {
return;
}
xmlNodePtr newchild = fragment->children;
xmlDocPtr doc = parentNode->doc;
if (newchild) {
xmlNodePtr last = fragment->last;
/* Unlink and free it unless it became a part of the fragment. */
if (child->parent != fragment) {
xmlUnlinkNode(child);
}
dom_pre_insert(insertion_point, parentNode, newchild, fragment);
dom_fragment_assign_parent_node(parentNode, fragment);
dom_reconcile_ns_list(doc, newchild, last);
}
xmlFree(fragment);
}
#endif

View File

@@ -1393,38 +1393,73 @@ void dom_set_old_ns(xmlDoc *doc, xmlNs *ns) {
}
/* }}} end dom_set_old_ns */
void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */
static void dom_reconcile_ns_internal(xmlDocPtr doc, xmlNodePtr nodep)
{
xmlNsPtr nsptr, nsdftptr, curns, prevns = NULL;
if (nodep->type == XML_ELEMENT_NODE) {
/* 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, nodep->parent, curns->href)) &&
(curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) {
curns->next = NULL;
if (prevns == NULL) {
nodep->nsDef = nsdftptr;
} else {
prevns->next = nsdftptr;
}
dom_set_old_ns(doc, curns);
curns = prevns;
/* 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, nodep->parent, curns->href)) &&
(curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) {
curns->next = NULL;
if (prevns == NULL) {
nodep->nsDef = nsdftptr;
} else {
prevns->next = nsdftptr;
}
dom_set_old_ns(doc, curns);
curns = prevns;
}
prevns = curns;
curns = nsdftptr;
}
prevns = curns;
curns = nsdftptr;
}
}
}
void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */
{
if (nodep->type == XML_ELEMENT_NODE) {
dom_reconcile_ns_internal(doc, nodep);
xmlReconciliateNs(doc, nodep);
}
}
/* }}} */
static void dom_reconcile_ns_list_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last)
{
ZEND_ASSERT(nodep != NULL);
while (true) {
if (nodep->type == XML_ELEMENT_NODE) {
dom_reconcile_ns_internal(doc, nodep);
if (nodep->children) {
dom_reconcile_ns_list_internal(doc, nodep->children, nodep->last /* process the whole children list */);
}
}
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);
/* Outside of the recursion above because xmlReconciliateNs() performs its own recursion. */
while (true) {
xmlReconciliateNs(doc, nodep);
if (nodep == last) {
break;
}
nodep = nodep->next;
}
}
/*
http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS

View File

@@ -118,6 +118,7 @@ int dom_check_qname(char *qname, char **localname, char **prefix, int uri_len, i
xmlNsPtr dom_get_ns(xmlNodePtr node, char *uri, int *errorcode, char *prefix);
void dom_set_old_ns(xmlDoc *doc, xmlNs *ns);
void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep);
void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last);
xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName);
void dom_normalize (xmlNodePtr nodep);
xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr basep, xmlNodePtr nodep, char *ns, char *local, int *cur, int index);
@@ -139,6 +140,7 @@ void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc);
void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc);
void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc);
void dom_child_node_remove(dom_object *context);
void dom_child_replace_with(dom_object *context, zval *nodes, int nodesc);
#define DOM_GET_OBJ(__ptr, __id, __prtype, __intern) { \
__intern = Z_DOMOBJ_P(__id); \

151
ext/dom/tests/bug67440.phpt Normal file
View File

@@ -0,0 +1,151 @@
--TEST--
Bug #67440 (append_node of a DOMDocumentFragment does not reconcile namespaces)
--EXTENSIONS--
dom
--FILE--
<?php
function createDocument() {
$document = new DOMDocument();
$document->loadXML('<?xml version="1.0"?><rootElement xmlns:myns="http://example/ns" xmlns:myns2="http://example/ns2"></rootElement>');
$fragment = $document->createDocumentFragment();
$fragment->appendChild($document->createTextNode("\n"));
$fragment->appendChild($document->createElementNS('http://example/ns', 'myns:childNode', '1'));
$fragment->appendChild($document->createTextNode("\n"));
$fragment->appendChild($document->createElementNS('http://example/ns', 'myns:childNode', '2'));
$fragment->appendChild($document->createTextNode("\n"));
return array($document, $fragment);
}
function case1($method) {
list($document, $fragment) = createDocument();
$document->documentElement->{$method}($fragment);
echo $document->saveXML();
}
function case2($method) {
list($document, $fragment) = createDocument();
$childNodes = iterator_to_array($fragment->childNodes);
foreach ($childNodes as $childNode) {
$document->documentElement->{$method}($childNode);
}
echo $document->saveXML();
}
function case3($method) {
list($document, $fragment) = createDocument();
$fragment->removeChild($fragment->firstChild);
$document->documentElement->{$method}($fragment);
echo $document->saveXML();
}
function case4($method) {
list($document, $fragment) = createDocument();
$fragment->childNodes[1]->appendChild($document->createElementNS('http://example/ns2', 'myns2:childNode', '3'));
$document->documentElement->{$method}($fragment);
echo $document->saveXML();
}
echo "== appendChild ==\n";
echo "-- fragment to document element --\n"; case1('appendChild'); echo "\n";
echo "-- children manually document element --\n"; case2('appendChild'); echo "\n";
echo "-- fragment to document where first element is not a text node --\n"; case3('appendChild'); echo "\n";
echo "-- fragment with namespace declarations in children --\n"; case4('appendChild'); echo "\n";
echo "== insertBefore ==\n";
echo "-- fragment to document element --\n"; case1('insertBefore'); echo "\n";
echo "-- children manually document element --\n"; case2('insertBefore'); echo "\n";
echo "-- fragment to document where first element is not a text node --\n"; case3('insertBefore'); echo "\n";
echo "-- fragment with namespace declarations in children --\n"; case4('insertBefore'); echo "\n";
echo "== insertAfter ==\n";
echo "-- fragment to document element --\n"; case1('insertBefore'); echo "\n";
echo "-- children manually document element --\n"; case2('insertBefore'); echo "\n";
echo "-- fragment to document where first element is not a text node --\n"; case3('insertBefore'); echo "\n";
echo "-- fragment with namespace declarations in children --\n"; case4('insertBefore'); echo "\n";
?>
--EXPECT--
== appendChild ==
-- fragment to document element --
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns" xmlns:myns2="http://example/ns2">
<myns:childNode>1</myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
-- children manually document element --
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns" xmlns:myns2="http://example/ns2">
<myns:childNode>1</myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
-- fragment to document where first element is not a text node --
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns" xmlns:myns2="http://example/ns2"><myns:childNode>1</myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
-- fragment with namespace declarations in children --
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns" xmlns:myns2="http://example/ns2">
<myns:childNode>1<myns2:childNode>3</myns2:childNode></myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
== insertBefore ==
-- fragment to document element --
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns" xmlns:myns2="http://example/ns2">
<myns:childNode>1</myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
-- children manually document element --
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns" xmlns:myns2="http://example/ns2">
<myns:childNode>1</myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
-- fragment to document where first element is not a text node --
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns" xmlns:myns2="http://example/ns2"><myns:childNode>1</myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
-- fragment with namespace declarations in children --
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns" xmlns:myns2="http://example/ns2">
<myns:childNode>1<myns2:childNode>3</myns2:childNode></myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
== insertAfter ==
-- fragment to document element --
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns" xmlns:myns2="http://example/ns2">
<myns:childNode>1</myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
-- children manually document element --
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns" xmlns:myns2="http://example/ns2">
<myns:childNode>1</myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
-- fragment to document where first element is not a text node --
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns" xmlns:myns2="http://example/ns2"><myns:childNode>1</myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
-- fragment with namespace declarations in children --
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns" xmlns:myns2="http://example/ns2">
<myns:childNode>1<myns2:childNode>3</myns2:childNode></myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>

View File

@@ -0,0 +1,40 @@
--TEST--
Bug #77686 (Removed elements are still returned by getElementById)
--EXTENSIONS--
dom
--FILE--
<?php
$doc = new DOMDocument;
$doc->loadHTML('<html id="htmlelement"><body id="x">before<div id="y">hello</div>after</body></html>');
$body = $doc->getElementById('x');
$div = $doc->getElementById('y');
var_dump($doc->getElementById('y')->textContent);
// Detached from document, should not find it anymore
$body->removeChild($div);
var_dump($doc->getElementById('y'));
// Added again, should find it
$body->appendChild($div);
var_dump($doc->getElementById('y')->textContent);
// Should find root element without a problem
var_dump($doc->getElementById('htmlelement')->textContent);
// Created element but not yet attached, should not find it before it is added
$new_element = $doc->createElement('p');
$new_element->textContent = 'my new text';
$new_element->setAttribute('id', 'myp');
var_dump($doc->getElementById('myp'));
$body->appendChild($new_element);
var_dump($doc->getElementById('myp')->textContent);
?>
--EXPECT--
string(5) "hello"
NULL
string(5) "hello"
string(16) "beforeafterhello"
NULL
string(11) "my new text"

View File

@@ -0,0 +1,49 @@
--TEST--
Bug #81642 (DOMChildNode::replaceWith() bug when replacing a node with itself)
--EXTENSIONS--
dom
--FILE--
<?php
// Replace with itself
$doc = new DOMDocument();
$doc->appendChild($target = $doc->createElement('test'));
$target->replaceWith($target);
var_dump($doc->saveXML());
// Replace with itself + another element
$doc = new DOMDocument();
$doc->appendChild($target = $doc->createElement('test'));
$target->replaceWith($target, $doc->createElement('foo'));
var_dump($doc->saveXML());
// Replace with text node
$doc = new DOMDocument();
$doc->appendChild($target = $doc->createElement('test'));
$target->replaceWith($target, 'foo');
var_dump($doc->saveXML());
// Replace with text node variant 2
$doc = new DOMDocument();
$doc->appendChild($target = $doc->createElement('test'));
$target->replaceWith('bar', $target, 'foo');
var_dump($doc->saveXML());
?>
--EXPECT--
string(30) "<?xml version="1.0"?>
<test/>
"
string(37) "<?xml version="1.0"?>
<test/>
<foo/>
"
string(34) "<?xml version="1.0"?>
<test/>
foo
"
string(38) "<?xml version="1.0"?>
bar
<test/>
foo
"