Skip to content

Commit 6560c9b

Browse files
committed
1 parent b24b351 commit 6560c9b

10 files changed

+202
-1
lines changed

NEWS

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ PHP NEWS
2020
. Added DOMElement::getAttributeNames(). (nielsdos)
2121
. Added DOMNode::getRootNode(). (nielsdos)
2222
. Added DOMElement::className. (nielsdos)
23+
. Added DOMParentNode::replaceChildren(). (nielsdos)
2324

2425
- Intl:
2526
. Fix memory leak in MessageFormatter::format() on failure. (Girgias)

UPGRADING

+1
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ PHP 8.3 UPGRADE NOTES
254254
yet.
255255
. Added DOMElement::className. This is not binary-safe at the moment
256256
because of underlying limitations of libxml2.
257+
. Added DOMParentNode::replaceChildren().
257258

258259
- JSON:
259260
. Added json_validate(), which returns whether the json is valid for

ext/dom/document.c

+19
Original file line numberDiff line numberDiff line change
@@ -2178,4 +2178,23 @@ PHP_METHOD(DOMDocument, prepend)
21782178
}
21792179
/* }}} */
21802180

2181+
/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
2182+
Since:
2183+
*/
2184+
PHP_METHOD(DOMDocument, replaceChildren)
2185+
{
2186+
uint32_t argc;
2187+
zval *args;
2188+
dom_object *intern;
2189+
2190+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "*", &args, &argc) == FAILURE) {
2191+
RETURN_THROWS();
2192+
}
2193+
2194+
DOM_GET_THIS_INTERN(intern);
2195+
2196+
dom_parent_node_replace_children(intern, args, argc);
2197+
}
2198+
/* }}} */
2199+
21812200
#endif /* HAVE_LIBXML && HAVE_DOM */

ext/dom/documentfragment.c

+19
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,23 @@ PHP_METHOD(DOMDocumentFragment, prepend)
130130
}
131131
/* }}} */
132132

133+
/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
134+
Since:
135+
*/
136+
PHP_METHOD(DOMDocumentFragment, replaceChildren)
137+
{
138+
uint32_t argc;
139+
zval *args;
140+
dom_object *intern;
141+
142+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "*", &args, &argc) == FAILURE) {
143+
RETURN_THROWS();
144+
}
145+
146+
DOM_GET_THIS_INTERN(intern);
147+
148+
dom_parent_node_replace_children(intern, args, argc);
149+
}
150+
/* }}} */
151+
133152
#endif

ext/dom/element.c

+19
Original file line numberDiff line numberDiff line change
@@ -1295,4 +1295,23 @@ PHP_METHOD(DOMElement, replaceWith)
12951295
}
12961296
/* }}} end DOMElement::prepend */
12971297

1298+
/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
1299+
Since:
1300+
*/
1301+
PHP_METHOD(DOMElement, replaceChildren)
1302+
{
1303+
uint32_t argc;
1304+
zval *args;
1305+
dom_object *intern;
1306+
1307+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "*", &args, &argc) == FAILURE) {
1308+
RETURN_THROWS();
1309+
}
1310+
1311+
DOM_GET_THIS_INTERN(intern);
1312+
1313+
dom_parent_node_replace_children(intern, args, argc);
1314+
}
1315+
/* }}} */
1316+
12981317
#endif

ext/dom/parentnode.c

+33
Original file line numberDiff line numberDiff line change
@@ -591,4 +591,37 @@ void dom_child_replace_with(dom_object *context, zval *nodes, uint32_t nodesc)
591591
xmlFree(fragment);
592592
}
593593

594+
void dom_parent_node_replace_children(dom_object *context, zval *nodes, uint32_t nodesc)
595+
{
596+
/* Spec link: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren */
597+
598+
xmlNodePtr thisp = dom_object_get_node(context);
599+
/* Note: Only rule 2 of pre-insertion validity can be broken */
600+
if (dom_hierarchy_node_list(thisp, nodes, nodesc)) {
601+
php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(context->document));
602+
return;
603+
}
604+
605+
xmlNodePtr fragment = dom_zvals_to_fragment(context->document, thisp, nodes, nodesc);
606+
if (UNEXPECTED(fragment == NULL)) {
607+
return;
608+
}
609+
610+
php_libxml_invalidate_node_list_cache_from_doc(context->document->ptr);
611+
612+
dom_remove_all_children(thisp);
613+
614+
xmlNodePtr newchild = fragment->children;
615+
if (newchild) {
616+
xmlNodePtr last = fragment->last;
617+
618+
dom_pre_insert(NULL, thisp, newchild, fragment);
619+
620+
dom_fragment_assign_parent_node(thisp, fragment);
621+
dom_reconcile_ns_list(thisp->doc, newchild, last);
622+
}
623+
624+
xmlFree(fragment);
625+
}
626+
594627
#endif

ext/dom/php_dom.h

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, uint32_t nodesc);
155155
void dom_parent_node_append(dom_object *context, zval *nodes, uint32_t nodesc);
156156
void dom_parent_node_after(dom_object *context, zval *nodes, uint32_t nodesc);
157157
void dom_parent_node_before(dom_object *context, zval *nodes, uint32_t nodesc);
158+
void dom_parent_node_replace_children(dom_object *context, zval *nodes, uint32_t nodesc);
158159
void dom_child_node_remove(dom_object *context);
159160
void dom_child_replace_with(dom_object *context, zval *nodes, uint32_t nodesc);
160161

ext/dom/php_dom.stub.php

+12
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,9 @@ public function append(...$nodes): void;
275275

276276
/** @param DOMNode|string $nodes */
277277
public function prepend(...$nodes): void;
278+
279+
/** @param DOMNode|string $nodes */
280+
public function replaceChildren(...$nodes): void;
278281
}
279282

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

460463
/** @param DOMNode|string $nodes */
461464
public function prepend(...$nodes): void {}
465+
466+
/** @param DOMNode|string $nodes */
467+
public function replaceChildren(...$nodes): void {}
462468
}
463469

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

637643
/** @param DOMNode|string $nodes */
638644
public function prepend(...$nodes): void {}
645+
646+
/** @param DOMNode|string $nodes */
647+
public function replaceChildren(...$nodes): void {}
639648
}
640649

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

804813
/** @param DOMNode|string $nodes */
805814
public function prepend(...$nodes): void {}
815+
816+
/** @param DOMNode|string $nodes */
817+
public function replaceChildren(...$nodes): void {}
806818
}
807819

808820
final class DOMException extends Exception

ext/dom/php_dom_arginfo.h

+16-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
--TEST--
2+
DOMParentNode::replaceChildren()
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = new DOMDocument();
9+
$dom->loadHTML('<!DOCTYPE HTML><html><p>hi</p> test <p>hi2</p></html>');
10+
11+
echo "--- Edge cases ---\n";
12+
13+
try {
14+
$dom->documentElement->replaceChildren($dom->documentElement);
15+
} catch (DOMException $e) {
16+
echo $e->getMessage(), "\n";
17+
}
18+
19+
try {
20+
$dom->documentElement->firstElementChild->replaceChildren($dom->documentElement);
21+
} catch (DOMException $e) {
22+
echo $e->getMessage(), "\n";
23+
}
24+
25+
echo "--- Normal cases ---\n";
26+
27+
$dom->documentElement->replaceChildren('foo', $dom->createElement('p'), 'bar');
28+
echo $dom->saveXML();
29+
30+
$fragment1 = $dom->createDocumentFragment();
31+
$fragment1->appendChild($dom->createElement('a'));
32+
$fragment1->appendChild($dom->createElement('b'));
33+
$fragment2 = $dom->createDocumentFragment();
34+
$fragment2->append('text');
35+
$fragment3 = $dom->createDocumentFragment();
36+
$dom->documentElement->replaceChildren($fragment1, $fragment2, $fragment3);
37+
echo $dom->saveXML();
38+
39+
echo "--- Fragment case ---\n";
40+
41+
$fragment = $dom->createDocumentFragment();
42+
$fragment->replaceChildren();
43+
var_dump($dom->saveXML($fragment));
44+
45+
$old = $fragment->appendChild($dom->createElement('p', 'test'));
46+
$fragment->replaceChildren($dom->createElement('b', 'test'));
47+
echo $dom->saveXML($fragment), "\n";
48+
var_dump($old->nodeValue);
49+
50+
echo "--- Idempotent case ---\n";
51+
52+
$dom->replaceChildren($dom->documentElement);
53+
echo $dom->saveXML();
54+
55+
echo "--- Removal shortcut ---\n";
56+
57+
$dom->documentElement->replaceChildren();
58+
echo $dom->saveXML();
59+
60+
?>
61+
--EXPECT--
62+
--- Edge cases ---
63+
Hierarchy Request Error
64+
Hierarchy Request Error
65+
--- Normal cases ---
66+
<?xml version="1.0" standalone="yes"?>
67+
<!DOCTYPE HTML>
68+
<html>foo<p/>bar</html>
69+
<?xml version="1.0" standalone="yes"?>
70+
<!DOCTYPE HTML>
71+
<html><a/><b/>text</html>
72+
--- Fragment case ---
73+
string(0) ""
74+
<b>test</b>
75+
string(4) "test"
76+
--- Idempotent case ---
77+
<?xml version="1.0" standalone="yes"?>
78+
<html><a/><b/>text</html>
79+
--- Removal shortcut ---
80+
<?xml version="1.0" standalone="yes"?>
81+
<html/>

0 commit comments

Comments
 (0)