Skip to content

Commit

Permalink
Implement DOMParentNode::replaceChildren()
Browse files Browse the repository at this point in the history
  • Loading branch information
nielsdos committed Jul 14, 2023
1 parent b24b351 commit 6560c9b
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 1 deletion.
1 change: 1 addition & 0 deletions NEWS
Expand Up @@ -20,6 +20,7 @@ PHP NEWS
. Added DOMElement::getAttributeNames(). (nielsdos)
. Added DOMNode::getRootNode(). (nielsdos)
. Added DOMElement::className. (nielsdos)
. Added DOMParentNode::replaceChildren(). (nielsdos)

- Intl:
. Fix memory leak in MessageFormatter::format() on failure. (Girgias)
Expand Down
1 change: 1 addition & 0 deletions UPGRADING
Expand Up @@ -254,6 +254,7 @@ PHP 8.3 UPGRADE NOTES
yet.
. Added DOMElement::className. This is not binary-safe at the moment
because of underlying limitations of libxml2.
. Added DOMParentNode::replaceChildren().

- JSON:
. Added json_validate(), which returns whether the json is valid for
Expand Down
19 changes: 19 additions & 0 deletions ext/dom/document.c
Expand Up @@ -2178,4 +2178,23 @@ PHP_METHOD(DOMDocument, prepend)
}
/* }}} */

/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
Since:
*/
PHP_METHOD(DOMDocument, replaceChildren)
{
uint32_t argc;
zval *args;
dom_object *intern;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "*", &args, &argc) == FAILURE) {
RETURN_THROWS();
}

DOM_GET_THIS_INTERN(intern);

dom_parent_node_replace_children(intern, args, argc);
}
/* }}} */

#endif /* HAVE_LIBXML && HAVE_DOM */
19 changes: 19 additions & 0 deletions ext/dom/documentfragment.c
Expand Up @@ -130,4 +130,23 @@ PHP_METHOD(DOMDocumentFragment, prepend)
}
/* }}} */

/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
Since:
*/
PHP_METHOD(DOMDocumentFragment, replaceChildren)
{
uint32_t argc;
zval *args;
dom_object *intern;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "*", &args, &argc) == FAILURE) {
RETURN_THROWS();
}

DOM_GET_THIS_INTERN(intern);

dom_parent_node_replace_children(intern, args, argc);
}
/* }}} */

#endif
19 changes: 19 additions & 0 deletions ext/dom/element.c
Expand Up @@ -1295,4 +1295,23 @@ PHP_METHOD(DOMElement, replaceWith)
}
/* }}} end DOMElement::prepend */

/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
Since:
*/
PHP_METHOD(DOMElement, replaceChildren)
{
uint32_t argc;
zval *args;
dom_object *intern;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "*", &args, &argc) == FAILURE) {
RETURN_THROWS();
}

DOM_GET_THIS_INTERN(intern);

dom_parent_node_replace_children(intern, args, argc);
}
/* }}} */

#endif
33 changes: 33 additions & 0 deletions ext/dom/parentnode.c
Expand Up @@ -591,4 +591,37 @@ void dom_child_replace_with(dom_object *context, zval *nodes, uint32_t nodesc)
xmlFree(fragment);
}

void dom_parent_node_replace_children(dom_object *context, zval *nodes, uint32_t nodesc)
{
/* Spec link: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren */

xmlNodePtr thisp = dom_object_get_node(context);
/* Note: Only rule 2 of pre-insertion validity can be broken */
if (dom_hierarchy_node_list(thisp, nodes, nodesc)) {
php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(context->document));
return;
}

xmlNodePtr fragment = dom_zvals_to_fragment(context->document, thisp, nodes, nodesc);
if (UNEXPECTED(fragment == NULL)) {
return;
}

php_libxml_invalidate_node_list_cache_from_doc(context->document->ptr);

dom_remove_all_children(thisp);

xmlNodePtr newchild = fragment->children;
if (newchild) {
xmlNodePtr last = fragment->last;

dom_pre_insert(NULL, thisp, newchild, fragment);

dom_fragment_assign_parent_node(thisp, fragment);
dom_reconcile_ns_list(thisp->doc, newchild, last);
}

xmlFree(fragment);
}

#endif
1 change: 1 addition & 0 deletions ext/dom/php_dom.h
Expand Up @@ -155,6 +155,7 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, uint32_t nodesc);
void dom_parent_node_append(dom_object *context, zval *nodes, uint32_t nodesc);
void dom_parent_node_after(dom_object *context, zval *nodes, uint32_t nodesc);
void dom_parent_node_before(dom_object *context, zval *nodes, uint32_t nodesc);
void dom_parent_node_replace_children(dom_object *context, zval *nodes, uint32_t nodesc);
void dom_child_node_remove(dom_object *context);
void dom_child_replace_with(dom_object *context, zval *nodes, uint32_t nodesc);

Expand Down
12 changes: 12 additions & 0 deletions ext/dom/php_dom.stub.php
Expand Up @@ -275,6 +275,9 @@ public function append(...$nodes): void;

/** @param DOMNode|string $nodes */
public function prepend(...$nodes): void;

/** @param DOMNode|string $nodes */
public function replaceChildren(...$nodes): void;
}

interface DOMChildNode
Expand Down Expand Up @@ -459,6 +462,9 @@ public function append(...$nodes): void {}

/** @param DOMNode|string $nodes */
public function prepend(...$nodes): void {}

/** @param DOMNode|string $nodes */
public function replaceChildren(...$nodes): void {}
}

class DOMNodeList implements IteratorAggregate, Countable
Expand Down Expand Up @@ -636,6 +642,9 @@ public function append(...$nodes): void {}

/** @param DOMNode|string $nodes */
public function prepend(...$nodes): void {}

/** @param DOMNode|string $nodes */
public function replaceChildren(...$nodes): void {}
}

class DOMDocument extends DOMNode implements DOMParentNode
Expand Down Expand Up @@ -803,6 +812,9 @@ public function append(...$nodes): void {}

/** @param DOMNode|string $nodes */
public function prepend(...$nodes): void {}

/** @param DOMNode|string $nodes */
public function replaceChildren(...$nodes): void {}
}

final class DOMException extends Exception
Expand Down
17 changes: 16 additions & 1 deletion ext/dom/php_dom_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 81 additions & 0 deletions ext/dom/tests/DOMElement_replaceChildren.phpt
@@ -0,0 +1,81 @@
--TEST--
DOMParentNode::replaceChildren()
--EXTENSIONS--
dom
--FILE--
<?php

$dom = new DOMDocument();
$dom->loadHTML('<!DOCTYPE HTML><html><p>hi</p> test <p>hi2</p></html>');

echo "--- Edge cases ---\n";

try {
$dom->documentElement->replaceChildren($dom->documentElement);
} catch (DOMException $e) {
echo $e->getMessage(), "\n";
}

try {
$dom->documentElement->firstElementChild->replaceChildren($dom->documentElement);
} catch (DOMException $e) {
echo $e->getMessage(), "\n";
}

echo "--- Normal cases ---\n";

$dom->documentElement->replaceChildren('foo', $dom->createElement('p'), 'bar');
echo $dom->saveXML();

$fragment1 = $dom->createDocumentFragment();
$fragment1->appendChild($dom->createElement('a'));
$fragment1->appendChild($dom->createElement('b'));
$fragment2 = $dom->createDocumentFragment();
$fragment2->append('text');
$fragment3 = $dom->createDocumentFragment();
$dom->documentElement->replaceChildren($fragment1, $fragment2, $fragment3);
echo $dom->saveXML();

echo "--- Fragment case ---\n";

$fragment = $dom->createDocumentFragment();
$fragment->replaceChildren();
var_dump($dom->saveXML($fragment));

$old = $fragment->appendChild($dom->createElement('p', 'test'));
$fragment->replaceChildren($dom->createElement('b', 'test'));
echo $dom->saveXML($fragment), "\n";
var_dump($old->nodeValue);

echo "--- Idempotent case ---\n";

$dom->replaceChildren($dom->documentElement);
echo $dom->saveXML();

echo "--- Removal shortcut ---\n";

$dom->documentElement->replaceChildren();
echo $dom->saveXML();

?>
--EXPECT--
--- Edge cases ---
Hierarchy Request Error
Hierarchy Request Error
--- Normal cases ---
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE HTML>
<html>foo<p/>bar</html>
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE HTML>
<html><a/><b/>text</html>
--- Fragment case ---
string(0) ""
<b>test</b>
string(4) "test"
--- Idempotent case ---
<?xml version="1.0" standalone="yes"?>
<html><a/><b/>text</html>
--- Removal shortcut ---
<?xml version="1.0" standalone="yes"?>
<html/>

0 comments on commit 6560c9b

Please sign in to comment.