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

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.
This commit is contained in:
Niels Dossche
2023-07-17 17:42:47 +02:00
committed by GitHub
parent d8696f9216
commit a73f38f407
9 changed files with 371 additions and 16 deletions

2
NEWS
View File

@@ -29,6 +29,8 @@ PHP NEWS
. Added DOMNode::parentElement and DOMNameSpaceNode::parentElement.
(nielsdos)
. Added DOMNode::isEqualNode(). (nielsdos)
. Added DOMElement::insertAdjacentElement() and
DOMElement::insertAdjacentText(). (nielsdos)
- FPM:
. Added warning to log when fpm socket was not registered on the expected

View File

@@ -279,6 +279,8 @@ PHP 8.3 UPGRADE NOTES
. Added DOMNode::isConnected and DOMNameSpaceNode::isConnected.
. Added DOMNode::parentElement and DOMNameSpaceNode::parentElement.
. Added DOMNode::isEqualNode().
. Added DOMElement::insertAdjacentElement() and
DOMElement::insertAdjacentText().
- JSON:
. Added json_validate(), which returns whether the json is valid for

View File

@@ -1050,6 +1050,26 @@ static void php_dom_transfer_document_ref(xmlNodePtr node, dom_object *dom_objec
}
}
bool php_dom_adopt_node(xmlNodePtr nodep, dom_object *dom_object_new_document, xmlDocPtr new_document)
{
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
if (nodep->doc != new_document) {
php_libxml_invalidate_node_list_cache_from_doc(new_document);
/* Note for ATTRIBUTE_NODE: specified is always true in ext/dom,
* and since this unlink it; the owner element will be unset (i.e. parentNode). */
int ret = xmlDOMWrapAdoptNode(NULL, nodep->doc, nodep, new_document, NULL, /* options, unused */ 0);
if (UNEXPECTED(ret != 0)) {
return false;
}
php_dom_transfer_document_ref(nodep, dom_object_new_document, new_document);
} else {
xmlUnlinkNode(nodep);
}
return true;
}
/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-Document3-adoptNode
Since: DOM Level 3
Modern spec URL: https://dom.spec.whatwg.org/#dom-document-adoptnode
@@ -1080,21 +1100,8 @@ PHP_METHOD(DOMDocument, adoptNode)
zval *new_document_zval = ZEND_THIS;
DOM_GET_OBJ(new_document, new_document_zval, xmlDocPtr, dom_object_new_document);
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
if (nodep->doc != new_document) {
php_libxml_invalidate_node_list_cache_from_doc(new_document);
/* Note for ATTRIBUTE_NODE: specified is always true in ext/dom,
* and since this unlink it; the owner element will be unset (i.e. parentNode). */
int ret = xmlDOMWrapAdoptNode(NULL, nodep->doc, nodep, new_document, NULL, /* options, unused */ 0);
if (UNEXPECTED(ret != 0)) {
RETURN_FALSE;
}
php_dom_transfer_document_ref(nodep, dom_object_new_document, new_document);
} else {
xmlUnlinkNode(nodep);
if (!php_dom_adopt_node(nodep, dom_object_new_document, new_document)) {
RETURN_FALSE;
}
RETURN_OBJ_COPY(&dom_object_nodep->std);

View File

@@ -1345,4 +1345,120 @@ PHP_METHOD(DOMElement, replaceChildren)
}
/* }}} */
#define INSERT_ADJACENT_RES_FAILED ((void*) -1)
static xmlNodePtr dom_insert_adjacent(const zend_string *where, xmlNodePtr thisp, dom_object *this_intern, xmlNodePtr otherp)
{
if (zend_string_equals_literal_ci(where, "beforebegin")) {
if (thisp->parent == NULL) {
return NULL;
}
if (dom_hierarchy(thisp->parent, otherp) == FAILURE) {
php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(this_intern->document));
return INSERT_ADJACENT_RES_FAILED;
}
if (!php_dom_adopt_node(otherp, this_intern, thisp->doc)) {
return INSERT_ADJACENT_RES_FAILED;
}
otherp = xmlAddPrevSibling(thisp, otherp);
} else if (zend_string_equals_literal_ci(where, "afterbegin")) {
if (dom_hierarchy(thisp, otherp) == FAILURE) {
php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(this_intern->document));
return INSERT_ADJACENT_RES_FAILED;
}
if (!php_dom_adopt_node(otherp, this_intern, thisp->doc)) {
return INSERT_ADJACENT_RES_FAILED;
}
if (thisp->children == NULL) {
otherp = xmlAddChild(thisp, otherp);
} else {
otherp = xmlAddPrevSibling(thisp->children, otherp);
}
} else if (zend_string_equals_literal_ci(where, "beforeend")) {
if (dom_hierarchy(thisp, otherp) == FAILURE) {
php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(this_intern->document));
return INSERT_ADJACENT_RES_FAILED;
}
if (!php_dom_adopt_node(otherp, this_intern, thisp->doc)) {
return INSERT_ADJACENT_RES_FAILED;
}
otherp = xmlAddChild(thisp, otherp);
} else if (zend_string_equals_literal_ci(where, "afterend")) {
if (thisp->parent == NULL) {
return NULL;
}
if (dom_hierarchy(thisp->parent, otherp) == FAILURE) {
php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(this_intern->document));
return INSERT_ADJACENT_RES_FAILED;
}
if (!php_dom_adopt_node(otherp, this_intern, thisp->doc)) {
return INSERT_ADJACENT_RES_FAILED;
}
otherp = xmlAddNextSibling(thisp, otherp);
} else {
php_dom_throw_error(SYNTAX_ERR, dom_get_strict_error(this_intern->document));
return INSERT_ADJACENT_RES_FAILED;
}
dom_reconcile_ns(thisp->doc, otherp);
return otherp;
}
/* {{{ URL: https://dom.spec.whatwg.org/#dom-element-insertadjacentelement
Since:
*/
PHP_METHOD(DOMElement, insertAdjacentElement)
{
zend_string *where;
zval *element_zval, *id;
xmlNodePtr thisp, otherp;
dom_object *this_intern, *other_intern;
int ret;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "SO", &where, &element_zval, dom_element_class_entry) == FAILURE) {
RETURN_THROWS();
}
DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, this_intern);
DOM_GET_OBJ(otherp, element_zval, xmlNodePtr, other_intern);
xmlNodePtr result = dom_insert_adjacent(where, thisp, this_intern, otherp);
if (result == NULL) {
RETURN_NULL();
} else if (result != INSERT_ADJACENT_RES_FAILED) {
DOM_RET_OBJ(otherp, &ret, other_intern);
} else {
RETURN_THROWS();
}
}
/* }}} end DOMElement::insertAdjacentElement */
/* {{{ URL: https://dom.spec.whatwg.org/#dom-element-insertadjacenttext
Since:
*/
PHP_METHOD(DOMElement, insertAdjacentText)
{
zend_string *where, *data;
dom_object *this_intern;
zval *id;
xmlNodePtr thisp;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &where, &data) == FAILURE) {
RETURN_THROWS();
}
DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, this_intern);
if (UNEXPECTED(ZEND_SIZE_T_INT_OVFL(ZSTR_LEN(data)))) {
zend_argument_value_error(2, "is too long");
RETURN_THROWS();
}
xmlNodePtr otherp = xmlNewDocTextLen(thisp->doc, (const xmlChar *) ZSTR_VAL(data), ZSTR_LEN(data));
xmlNodePtr result = dom_insert_adjacent(where, thisp, this_intern, otherp);
if (result == NULL || result == INSERT_ADJACENT_RES_FAILED) {
xmlFreeNode(otherp);
}
}
/* }}} end DOMElement::insertAdjacentText */
#endif

View File

@@ -151,6 +151,7 @@ void php_dom_get_content_into_zval(const xmlNode *nodep, zval *target, bool defa
zend_string *dom_node_concatenated_name_helper(size_t name_len, const char *name, size_t prefix_len, const char *prefix);
zend_string *dom_node_get_node_name_attribute_or_element(const xmlNode *nodep);
bool php_dom_is_node_connected(const xmlNode *node);
bool php_dom_adopt_node(xmlNodePtr nodep, dom_object *dom_object_new_document, xmlDocPtr new_document);
/* parentnode */
void dom_parent_node_prepend(dom_object *context, zval *nodes, uint32_t nodesc);

View File

@@ -661,6 +661,10 @@ class DOMElement extends DOMNode implements DOMParentNode, DOMChildNode
/** @param DOMNode|string $nodes */
public function replaceChildren(...$nodes): void {}
public function insertAdjacentElement(string $where, DOMElement $element): ?DOMElement {}
public function insertAdjacentText(string $where, string $data): void {}
}
class DOMDocument extends DOMNode implements DOMParentNode

View File

@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 7070b07b2dee16222242b7e516372a6562d87036 */
* Stub hash: 850ab297bd3e6162e0497769cace87a41e8e8a00 */
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0)
ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0)
@@ -296,6 +296,16 @@ ZEND_END_ARG_INFO()
#define arginfo_class_DOMElement_replaceChildren arginfo_class_DOMParentNode_append
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_DOMElement_insertAdjacentElement, 0, 2, DOMElement, 1)
ZEND_ARG_TYPE_INFO(0, where, IS_STRING, 0)
ZEND_ARG_OBJ_INFO(0, element, DOMElement, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMElement_insertAdjacentText, 0, 2, IS_VOID, 0)
ZEND_ARG_TYPE_INFO(0, where, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DOMDocument___construct, 0, 0, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, version, IS_STRING, 0, "\"1.0\"")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, encoding, IS_STRING, 0, "\"\"")
@@ -588,6 +598,8 @@ ZEND_METHOD(DOMElement, replaceWith);
ZEND_METHOD(DOMElement, append);
ZEND_METHOD(DOMElement, prepend);
ZEND_METHOD(DOMElement, replaceChildren);
ZEND_METHOD(DOMElement, insertAdjacentElement);
ZEND_METHOD(DOMElement, insertAdjacentText);
ZEND_METHOD(DOMDocument, __construct);
ZEND_METHOD(DOMDocument, createAttribute);
ZEND_METHOD(DOMDocument, createAttributeNS);
@@ -812,6 +824,8 @@ static const zend_function_entry class_DOMElement_methods[] = {
ZEND_ME(DOMElement, append, arginfo_class_DOMElement_append, ZEND_ACC_PUBLIC)
ZEND_ME(DOMElement, prepend, arginfo_class_DOMElement_prepend, ZEND_ACC_PUBLIC)
ZEND_ME(DOMElement, replaceChildren, arginfo_class_DOMElement_replaceChildren, ZEND_ACC_PUBLIC)
ZEND_ME(DOMElement, insertAdjacentElement, arginfo_class_DOMElement_insertAdjacentElement, ZEND_ACC_PUBLIC)
ZEND_ME(DOMElement, insertAdjacentText, arginfo_class_DOMElement_insertAdjacentText, ZEND_ACC_PUBLIC)
ZEND_FE_END
};

View File

@@ -0,0 +1,128 @@
--TEST--
DOMElement::insertAdjacentElement()
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$dom->loadXML('<?xml version="1.0"?><container><p>foo</p></container>');
$container = $dom->documentElement;
$p = $container->firstElementChild;
echo "--- Edge cases ---\n";
var_dump($dom->createElement('free')->insertAdjacentElement("beforebegin", $dom->createElement('element')));
var_dump($dom->createElement('free')->insertAdjacentElement("afterend", $dom->createElement('element')));
try {
var_dump($dom->createElement('free')->insertAdjacentElement("bogus", $dom->createElement('element')));
} catch (DOMException $e) {
echo $e->getMessage(), "\n";
}
echo "--- Hierarchy test ---\n";
$element = $dom->createElement('free');
$child = $element->appendChild($dom->createElement('child'));
foreach (['beforebegin', 'afterbegin', 'beforeend', 'afterend'] as $where) {
try {
var_dump($child->insertAdjacentElement($where, $element)->tagName);
} catch (DOMException $e) {
echo $e->getMessage(), "\n";
}
}
function testNormalCases($dom, $uppercase) {
$container = $dom->documentElement;
$p = $container->firstElementChild;
$transform = fn ($s) => $uppercase ? strtoupper($s) : $s;
var_dump($p->insertAdjacentElement($transform("beforebegin"), $dom->createElement('A'))->tagName);
echo $dom->saveXML();
var_dump($p->insertAdjacentElement($transform("afterbegin"), $dom->createElement('B'))->tagName);
echo $dom->saveXML();
var_dump($p->insertAdjacentElement($transform("beforeend"), $dom->createElement('C'))->tagName);
echo $dom->saveXML();
var_dump($p->insertAdjacentElement($transform("afterend"), $dom->createElement('D'))->tagName);
echo $dom->saveXML();
}
echo "--- Normal cases uppercase ---\n";
testNormalCases(clone $dom, true);
echo "--- Normal cases lowercase ---\n";
testNormalCases($dom, false);
$empty = $dom->createElement('empty');
var_dump($empty->insertAdjacentElement("afterbegin", $dom->createElement('A'))->tagName);
echo $dom->saveXML($empty), "\n";
echo "--- Namespace test ---\n";
$dom->loadXML('<?xml version="1.0"?><container xmlns:foo="some:ns"/>');
$dom->documentElement->insertAdjacentElement("afterbegin", $dom->createElementNS("some:ns", "bar"));
echo $dom->saveXML();
echo "--- Two document test ---\n";
$dom1 = new DOMDocument();
$dom1->loadXML('<?xml version="1.0"?><container><div/></container>');
$dom2 = new DOMDocument();
$dom2->loadXML('<?xml version="1.0"?><container><p/></container>');
$dom1->documentElement->firstChild->insertAdjacentElement('afterbegin', $dom2->documentElement->firstChild);
echo $dom1->saveXML();
echo $dom2->saveXML();
?>
--EXPECT--
--- Edge cases ---
NULL
NULL
Syntax Error
--- Hierarchy test ---
Hierarchy Request Error
Hierarchy Request Error
Hierarchy Request Error
Hierarchy Request Error
--- Normal cases uppercase ---
string(1) "A"
<?xml version="1.0"?>
<container><A/><p>foo</p></container>
string(1) "B"
<?xml version="1.0"?>
<container><A/><p><B/>foo</p></container>
string(1) "C"
<?xml version="1.0"?>
<container><A/><p><B/>foo<C/></p></container>
string(1) "D"
<?xml version="1.0"?>
<container><A/><p><B/>foo<C/></p><D/></container>
--- Normal cases lowercase ---
string(1) "A"
<?xml version="1.0"?>
<container><A/><p>foo</p></container>
string(1) "B"
<?xml version="1.0"?>
<container><A/><p><B/>foo</p></container>
string(1) "C"
<?xml version="1.0"?>
<container><A/><p><B/>foo<C/></p></container>
string(1) "D"
<?xml version="1.0"?>
<container><A/><p><B/>foo<C/></p><D/></container>
string(1) "A"
<empty><A/></empty>
--- Namespace test ---
<?xml version="1.0"?>
<container xmlns:foo="some:ns"><foo:bar/></container>
--- Two document test ---
<?xml version="1.0"?>
<container><div><p/></div></container>
<?xml version="1.0"?>
<container/>

View File

@@ -0,0 +1,81 @@
--TEST--
DOMElement::insertAdjacentText()
--EXTENSIONS--
dom
--FILE--
<?php
$dom = new DOMDocument();
$dom->loadXML('<?xml version="1.0"?><container><p>foo</p></container>');
echo "--- Edge cases ---\n";
try {
$dom->createElement('free')->insertAdjacentText("bogus", "bogus");
} catch (DOMException $e) {
echo $e->getMessage(), "\n";
}
function testNormalCases($dom, $uppercase) {
$container = $dom->documentElement;
$p = $container->firstElementChild;
$transform = fn ($s) => $uppercase ? strtoupper($s) : $s;
$p->insertAdjacentText("beforebegin", 'A');
echo $dom->saveXML();
$p->insertAdjacentText("afterbegin", 'B');
echo $dom->saveXML();
$p->insertAdjacentText("beforeend", 'C');
echo $dom->saveXML();
$p->insertAdjacentText("afterend", 'D');
echo $dom->saveXML();
}
echo "--- Normal cases uppercase ---\n";
testNormalCases(clone $dom, true);
echo "--- Normal cases lowercase ---\n";
testNormalCases($dom, false);
echo "--- Normal cases starting from empty element ---\n";
$empty = $dom->createElement('empty');
$empty->insertAdjacentText("afterbegin", 'A');
echo $dom->saveXML($empty), "\n";
$AText = $empty->firstChild;
$empty->insertAdjacentText("afterbegin", 'B');
echo $dom->saveXML($empty), "\n";
var_dump($AText->textContent);
?>
--EXPECT--
--- Edge cases ---
Syntax Error
--- Normal cases uppercase ---
<?xml version="1.0"?>
<container>A<p>foo</p></container>
<?xml version="1.0"?>
<container>A<p>Bfoo</p></container>
<?xml version="1.0"?>
<container>A<p>BfooC</p></container>
<?xml version="1.0"?>
<container>A<p>BfooC</p>D</container>
--- Normal cases lowercase ---
<?xml version="1.0"?>
<container>A<p>foo</p></container>
<?xml version="1.0"?>
<container>A<p>Bfoo</p></container>
<?xml version="1.0"?>
<container>A<p>BfooC</p></container>
<?xml version="1.0"?>
<container>A<p>BfooC</p>D</container>
--- Normal cases starting from empty element ---
<empty>A</empty>
<empty>BA</empty>
string(2) "BA"