Skip to content

Commit 52eee6f

Browse files
committed
1 parent 2f318cf commit 52eee6f

File tree

9 files changed

+322
-16
lines changed

9 files changed

+322
-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
@@ -271,6 +271,8 @@ PHP 8.3 UPGRADE NOTES
271271
. Added DOMNode::isConnected and DOMNameSpaceNode::isConnected.
272272
. Added DOMNode::parentElement and DOMNameSpaceNode::parentElement.
273273
. Added DOMNode::isEqualNode().
274+
. Added DOMElement::insertAdjacentElement() and
275+
DOMElement::insertAdjacentText().
274276

275277
- JSON:
276278
. 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: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,4 +1345,118 @@ 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+
}
1430+
}
1431+
/* }}} end DOMElement::insertAdjacentElement */
1432+
1433+
/* {{{ URL: https://dom.spec.whatwg.org/#dom-element-insertadjacenttext
1434+
Since:
1435+
*/
1436+
PHP_METHOD(DOMElement, insertAdjacentText)
1437+
{
1438+
zend_string *where, *data;
1439+
dom_object *this_intern;
1440+
zval *id;
1441+
xmlNodePtr thisp;
1442+
1443+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &where, &data) == FAILURE) {
1444+
RETURN_THROWS();
1445+
}
1446+
1447+
DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, this_intern);
1448+
1449+
if (UNEXPECTED(ZEND_SIZE_T_INT_OVFL(ZSTR_LEN(data)))) {
1450+
zend_argument_value_error(2, "is too long");
1451+
RETURN_THROWS();
1452+
}
1453+
1454+
xmlNodePtr otherp = xmlNewDocTextLen(thisp->doc, (const xmlChar *) ZSTR_VAL(data), ZSTR_LEN(data));
1455+
xmlNodePtr result = dom_insert_adjacent(where, thisp, this_intern, otherp);
1456+
if (result == NULL || result == INSERT_ADJACENT_RES_FAILED) {
1457+
xmlFreeNode(otherp);
1458+
}
1459+
}
1460+
/* }}} end DOMElement::insertAdjacentText */
1461+
13481462
#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: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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+
echo "--- Normal cases ---\n";
37+
38+
var_dump($p->insertAdjacentElement("beforebegin", $dom->createElement('A'))->tagName);
39+
echo $dom->saveXML();
40+
41+
var_dump($p->insertAdjacentElement("afterbegin", $dom->createElement('B'))->tagName);
42+
echo $dom->saveXML();
43+
44+
var_dump($p->insertAdjacentElement("beforeend", $dom->createElement('C'))->tagName);
45+
echo $dom->saveXML();
46+
47+
var_dump($p->insertAdjacentElement("afterend", $dom->createElement('D'))->tagName);
48+
echo $dom->saveXML();
49+
50+
$empty = $dom->createElement('empty');
51+
var_dump($empty->insertAdjacentElement("afterbegin", $dom->createElement('A'))->tagName);
52+
echo $dom->saveXML($empty), "\n";
53+
54+
echo "--- Namespace test ---\n";
55+
56+
$dom->loadXML('<?xml version="1.0"?><container xmlns:foo="some:ns"/>');
57+
$dom->documentElement->insertAdjacentElement("afterbegin", $dom->createElementNS("some:ns", "bar"));
58+
echo $dom->saveXML();
59+
60+
echo "--- Two document test ---\n";
61+
62+
$dom1 = new DOMDocument();
63+
$dom1->loadXML('<?xml version="1.0"?><container><div/></container>');
64+
$dom2 = new DOMDocument();
65+
$dom2->loadXML('<?xml version="1.0"?><container><p/></container>');
66+
$dom1->documentElement->firstChild->insertAdjacentElement('afterbegin', $dom2->documentElement->firstChild);
67+
echo $dom1->saveXML();
68+
echo $dom2->saveXML();
69+
70+
?>
71+
--EXPECT--
72+
--- Edge cases ---
73+
NULL
74+
NULL
75+
Syntax Error
76+
--- Hierarchy test ---
77+
Hierarchy Request Error
78+
Hierarchy Request Error
79+
Hierarchy Request Error
80+
Hierarchy Request Error
81+
--- Normal cases ---
82+
string(1) "A"
83+
<?xml version="1.0"?>
84+
<container><A/><p>foo</p></container>
85+
string(1) "B"
86+
<?xml version="1.0"?>
87+
<container><A/><p><B/>foo</p></container>
88+
string(1) "C"
89+
<?xml version="1.0"?>
90+
<container><A/><p><B/>foo<C/></p></container>
91+
string(1) "D"
92+
<?xml version="1.0"?>
93+
<container><A/><p><B/>foo<C/></p><D/></container>
94+
string(1) "A"
95+
<empty><A/></empty>
96+
--- Namespace test ---
97+
<?xml version="1.0"?>
98+
<container xmlns:foo="some:ns"><foo:bar/></container>
99+
--- Two document test ---
100+
<?xml version="1.0"?>
101+
<container><div><p/></div></container>
102+
<?xml version="1.0"?>
103+
<container/>

0 commit comments

Comments
 (0)