Skip to content

Commit a73f38f

Browse files
authored
Implement DOMElement::insertAdjacent{Element,Text} (#11700)
* Implement DOMElement::insertAdjacent{Element,Text} ref: https://dom.spec.whatwg.org/#dom-element-insertadjacentelement ref: https://dom.spec.whatwg.org/#dom-element-insertadjacenttext Closes GH-11700.
1 parent d8696f9 commit a73f38f

File tree

9 files changed

+371
-16
lines changed

9 files changed

+371
-16
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ PHP NEWS
2929
. Added DOMNode::parentElement and DOMNameSpaceNode::parentElement.
3030
(nielsdos)
3131
. Added DOMNode::isEqualNode(). (nielsdos)
32+
. Added DOMElement::insertAdjacentElement() and
33+
DOMElement::insertAdjacentText(). (nielsdos)
3234

3335
- FPM:
3436
. Added warning to log when fpm socket was not registered on the expected

UPGRADING

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ PHP 8.3 UPGRADE NOTES
279279
. Added DOMNode::isConnected and DOMNameSpaceNode::isConnected.
280280
. Added DOMNode::parentElement and DOMNameSpaceNode::parentElement.
281281
. Added DOMNode::isEqualNode().
282+
. Added DOMElement::insertAdjacentElement() and
283+
DOMElement::insertAdjacentText().
282284

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

ext/dom/document.c

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,26 @@ static void php_dom_transfer_document_ref(xmlNodePtr node, dom_object *dom_objec
10501050
}
10511051
}
10521052

1053+
bool php_dom_adopt_node(xmlNodePtr nodep, dom_object *dom_object_new_document, xmlDocPtr new_document)
1054+
{
1055+
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
1056+
if (nodep->doc != new_document) {
1057+
php_libxml_invalidate_node_list_cache_from_doc(new_document);
1058+
1059+
/* Note for ATTRIBUTE_NODE: specified is always true in ext/dom,
1060+
* and since this unlink it; the owner element will be unset (i.e. parentNode). */
1061+
int ret = xmlDOMWrapAdoptNode(NULL, nodep->doc, nodep, new_document, NULL, /* options, unused */ 0);
1062+
if (UNEXPECTED(ret != 0)) {
1063+
return false;
1064+
}
1065+
1066+
php_dom_transfer_document_ref(nodep, dom_object_new_document, new_document);
1067+
} else {
1068+
xmlUnlinkNode(nodep);
1069+
}
1070+
return true;
1071+
}
1072+
10531073
/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-Document3-adoptNode
10541074
Since: DOM Level 3
10551075
Modern spec URL: https://dom.spec.whatwg.org/#dom-document-adoptnode
@@ -1080,21 +1100,8 @@ PHP_METHOD(DOMDocument, adoptNode)
10801100
zval *new_document_zval = ZEND_THIS;
10811101
DOM_GET_OBJ(new_document, new_document_zval, xmlDocPtr, dom_object_new_document);
10821102

1083-
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
1084-
1085-
if (nodep->doc != new_document) {
1086-
php_libxml_invalidate_node_list_cache_from_doc(new_document);
1087-
1088-
/* Note for ATTRIBUTE_NODE: specified is always true in ext/dom,
1089-
* and since this unlink it; the owner element will be unset (i.e. parentNode). */
1090-
int ret = xmlDOMWrapAdoptNode(NULL, nodep->doc, nodep, new_document, NULL, /* options, unused */ 0);
1091-
if (UNEXPECTED(ret != 0)) {
1092-
RETURN_FALSE;
1093-
}
1094-
1095-
php_dom_transfer_document_ref(nodep, dom_object_new_document, new_document);
1096-
} else {
1097-
xmlUnlinkNode(nodep);
1103+
if (!php_dom_adopt_node(nodep, dom_object_new_document, new_document)) {
1104+
RETURN_FALSE;
10981105
}
10991106

11001107
RETURN_OBJ_COPY(&dom_object_nodep->std);

ext/dom/element.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,4 +1345,120 @@ PHP_METHOD(DOMElement, replaceChildren)
13451345
}
13461346
/* }}} */
13471347

1348+
#define INSERT_ADJACENT_RES_FAILED ((void*) -1)
1349+
1350+
static xmlNodePtr dom_insert_adjacent(const zend_string *where, xmlNodePtr thisp, dom_object *this_intern, xmlNodePtr otherp)
1351+
{
1352+
if (zend_string_equals_literal_ci(where, "beforebegin")) {
1353+
if (thisp->parent == NULL) {
1354+
return NULL;
1355+
}
1356+
if (dom_hierarchy(thisp->parent, otherp) == FAILURE) {
1357+
php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(this_intern->document));
1358+
return INSERT_ADJACENT_RES_FAILED;
1359+
}
1360+
if (!php_dom_adopt_node(otherp, this_intern, thisp->doc)) {
1361+
return INSERT_ADJACENT_RES_FAILED;
1362+
}
1363+
otherp = xmlAddPrevSibling(thisp, otherp);
1364+
} else if (zend_string_equals_literal_ci(where, "afterbegin")) {
1365+
if (dom_hierarchy(thisp, otherp) == FAILURE) {
1366+
php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(this_intern->document));
1367+
return INSERT_ADJACENT_RES_FAILED;
1368+
}
1369+
if (!php_dom_adopt_node(otherp, this_intern, thisp->doc)) {
1370+
return INSERT_ADJACENT_RES_FAILED;
1371+
}
1372+
if (thisp->children == NULL) {
1373+
otherp = xmlAddChild(thisp, otherp);
1374+
} else {
1375+
otherp = xmlAddPrevSibling(thisp->children, otherp);
1376+
}
1377+
} else if (zend_string_equals_literal_ci(where, "beforeend")) {
1378+
if (dom_hierarchy(thisp, otherp) == FAILURE) {
1379+
php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(this_intern->document));
1380+
return INSERT_ADJACENT_RES_FAILED;
1381+
}
1382+
if (!php_dom_adopt_node(otherp, this_intern, thisp->doc)) {
1383+
return INSERT_ADJACENT_RES_FAILED;
1384+
}
1385+
otherp = xmlAddChild(thisp, otherp);
1386+
} else if (zend_string_equals_literal_ci(where, "afterend")) {
1387+
if (thisp->parent == NULL) {
1388+
return NULL;
1389+
}
1390+
if (dom_hierarchy(thisp->parent, otherp) == FAILURE) {
1391+
php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(this_intern->document));
1392+
return INSERT_ADJACENT_RES_FAILED;
1393+
}
1394+
if (!php_dom_adopt_node(otherp, this_intern, thisp->doc)) {
1395+
return INSERT_ADJACENT_RES_FAILED;
1396+
}
1397+
otherp = xmlAddNextSibling(thisp, otherp);
1398+
} else {
1399+
php_dom_throw_error(SYNTAX_ERR, dom_get_strict_error(this_intern->document));
1400+
return INSERT_ADJACENT_RES_FAILED;
1401+
}
1402+
dom_reconcile_ns(thisp->doc, otherp);
1403+
return otherp;
1404+
}
1405+
1406+
/* {{{ URL: https://dom.spec.whatwg.org/#dom-element-insertadjacentelement
1407+
Since:
1408+
*/
1409+
PHP_METHOD(DOMElement, insertAdjacentElement)
1410+
{
1411+
zend_string *where;
1412+
zval *element_zval, *id;
1413+
xmlNodePtr thisp, otherp;
1414+
dom_object *this_intern, *other_intern;
1415+
int ret;
1416+
1417+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "SO", &where, &element_zval, dom_element_class_entry) == FAILURE) {
1418+
RETURN_THROWS();
1419+
}
1420+
1421+
DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, this_intern);
1422+
DOM_GET_OBJ(otherp, element_zval, xmlNodePtr, other_intern);
1423+
1424+
xmlNodePtr result = dom_insert_adjacent(where, thisp, this_intern, otherp);
1425+
if (result == NULL) {
1426+
RETURN_NULL();
1427+
} else if (result != INSERT_ADJACENT_RES_FAILED) {
1428+
DOM_RET_OBJ(otherp, &ret, other_intern);
1429+
} else {
1430+
RETURN_THROWS();
1431+
}
1432+
}
1433+
/* }}} end DOMElement::insertAdjacentElement */
1434+
1435+
/* {{{ URL: https://dom.spec.whatwg.org/#dom-element-insertadjacenttext
1436+
Since:
1437+
*/
1438+
PHP_METHOD(DOMElement, insertAdjacentText)
1439+
{
1440+
zend_string *where, *data;
1441+
dom_object *this_intern;
1442+
zval *id;
1443+
xmlNodePtr thisp;
1444+
1445+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &where, &data) == FAILURE) {
1446+
RETURN_THROWS();
1447+
}
1448+
1449+
DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, this_intern);
1450+
1451+
if (UNEXPECTED(ZEND_SIZE_T_INT_OVFL(ZSTR_LEN(data)))) {
1452+
zend_argument_value_error(2, "is too long");
1453+
RETURN_THROWS();
1454+
}
1455+
1456+
xmlNodePtr otherp = xmlNewDocTextLen(thisp->doc, (const xmlChar *) ZSTR_VAL(data), ZSTR_LEN(data));
1457+
xmlNodePtr result = dom_insert_adjacent(where, thisp, this_intern, otherp);
1458+
if (result == NULL || result == INSERT_ADJACENT_RES_FAILED) {
1459+
xmlFreeNode(otherp);
1460+
}
1461+
}
1462+
/* }}} end DOMElement::insertAdjacentText */
1463+
13481464
#endif

ext/dom/php_dom.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ void php_dom_get_content_into_zval(const xmlNode *nodep, zval *target, bool defa
151151
zend_string *dom_node_concatenated_name_helper(size_t name_len, const char *name, size_t prefix_len, const char *prefix);
152152
zend_string *dom_node_get_node_name_attribute_or_element(const xmlNode *nodep);
153153
bool php_dom_is_node_connected(const xmlNode *node);
154+
bool php_dom_adopt_node(xmlNodePtr nodep, dom_object *dom_object_new_document, xmlDocPtr new_document);
154155

155156
/* parentnode */
156157
void dom_parent_node_prepend(dom_object *context, zval *nodes, uint32_t nodesc);

ext/dom/php_dom.stub.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,10 @@ public function prepend(...$nodes): void {}
661661

662662
/** @param DOMNode|string $nodes */
663663
public function replaceChildren(...$nodes): void {}
664+
665+
public function insertAdjacentElement(string $where, DOMElement $element): ?DOMElement {}
666+
667+
public function insertAdjacentText(string $where, string $data): void {}
664668
}
665669

666670
class DOMDocument extends DOMNode implements DOMParentNode

ext/dom/php_dom_arginfo.h

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
--TEST--
2+
DOMElement::insertAdjacentElement()
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = new DOMDocument();
9+
$dom->loadXML('<?xml version="1.0"?><container><p>foo</p></container>');
10+
$container = $dom->documentElement;
11+
$p = $container->firstElementChild;
12+
13+
echo "--- Edge cases ---\n";
14+
15+
var_dump($dom->createElement('free')->insertAdjacentElement("beforebegin", $dom->createElement('element')));
16+
var_dump($dom->createElement('free')->insertAdjacentElement("afterend", $dom->createElement('element')));
17+
18+
try {
19+
var_dump($dom->createElement('free')->insertAdjacentElement("bogus", $dom->createElement('element')));
20+
} catch (DOMException $e) {
21+
echo $e->getMessage(), "\n";
22+
}
23+
24+
echo "--- Hierarchy test ---\n";
25+
26+
$element = $dom->createElement('free');
27+
$child = $element->appendChild($dom->createElement('child'));
28+
foreach (['beforebegin', 'afterbegin', 'beforeend', 'afterend'] as $where) {
29+
try {
30+
var_dump($child->insertAdjacentElement($where, $element)->tagName);
31+
} catch (DOMException $e) {
32+
echo $e->getMessage(), "\n";
33+
}
34+
}
35+
36+
function testNormalCases($dom, $uppercase) {
37+
$container = $dom->documentElement;
38+
$p = $container->firstElementChild;
39+
$transform = fn ($s) => $uppercase ? strtoupper($s) : $s;
40+
41+
var_dump($p->insertAdjacentElement($transform("beforebegin"), $dom->createElement('A'))->tagName);
42+
echo $dom->saveXML();
43+
44+
var_dump($p->insertAdjacentElement($transform("afterbegin"), $dom->createElement('B'))->tagName);
45+
echo $dom->saveXML();
46+
47+
var_dump($p->insertAdjacentElement($transform("beforeend"), $dom->createElement('C'))->tagName);
48+
echo $dom->saveXML();
49+
50+
var_dump($p->insertAdjacentElement($transform("afterend"), $dom->createElement('D'))->tagName);
51+
echo $dom->saveXML();
52+
}
53+
54+
echo "--- Normal cases uppercase ---\n";
55+
56+
testNormalCases(clone $dom, true);
57+
58+
echo "--- Normal cases lowercase ---\n";
59+
60+
testNormalCases($dom, false);
61+
62+
$empty = $dom->createElement('empty');
63+
var_dump($empty->insertAdjacentElement("afterbegin", $dom->createElement('A'))->tagName);
64+
echo $dom->saveXML($empty), "\n";
65+
66+
echo "--- Namespace test ---\n";
67+
68+
$dom->loadXML('<?xml version="1.0"?><container xmlns:foo="some:ns"/>');
69+
$dom->documentElement->insertAdjacentElement("afterbegin", $dom->createElementNS("some:ns", "bar"));
70+
echo $dom->saveXML();
71+
72+
echo "--- Two document test ---\n";
73+
74+
$dom1 = new DOMDocument();
75+
$dom1->loadXML('<?xml version="1.0"?><container><div/></container>');
76+
$dom2 = new DOMDocument();
77+
$dom2->loadXML('<?xml version="1.0"?><container><p/></container>');
78+
$dom1->documentElement->firstChild->insertAdjacentElement('afterbegin', $dom2->documentElement->firstChild);
79+
echo $dom1->saveXML();
80+
echo $dom2->saveXML();
81+
82+
?>
83+
--EXPECT--
84+
--- Edge cases ---
85+
NULL
86+
NULL
87+
Syntax Error
88+
--- Hierarchy test ---
89+
Hierarchy Request Error
90+
Hierarchy Request Error
91+
Hierarchy Request Error
92+
Hierarchy Request Error
93+
--- Normal cases uppercase ---
94+
string(1) "A"
95+
<?xml version="1.0"?>
96+
<container><A/><p>foo</p></container>
97+
string(1) "B"
98+
<?xml version="1.0"?>
99+
<container><A/><p><B/>foo</p></container>
100+
string(1) "C"
101+
<?xml version="1.0"?>
102+
<container><A/><p><B/>foo<C/></p></container>
103+
string(1) "D"
104+
<?xml version="1.0"?>
105+
<container><A/><p><B/>foo<C/></p><D/></container>
106+
--- Normal cases lowercase ---
107+
string(1) "A"
108+
<?xml version="1.0"?>
109+
<container><A/><p>foo</p></container>
110+
string(1) "B"
111+
<?xml version="1.0"?>
112+
<container><A/><p><B/>foo</p></container>
113+
string(1) "C"
114+
<?xml version="1.0"?>
115+
<container><A/><p><B/>foo<C/></p></container>
116+
string(1) "D"
117+
<?xml version="1.0"?>
118+
<container><A/><p><B/>foo<C/></p><D/></container>
119+
string(1) "A"
120+
<empty><A/></empty>
121+
--- Namespace test ---
122+
<?xml version="1.0"?>
123+
<container xmlns:foo="some:ns"><foo:bar/></container>
124+
--- Two document test ---
125+
<?xml version="1.0"?>
126+
<container><div><p/></div></container>
127+
<?xml version="1.0"?>
128+
<container/>

0 commit comments

Comments
 (0)