Add Dom\Element::insertAdjacentHTML() (#16614)

This commit is contained in:
Niels Dossche 2024-11-09 10:52:06 +01:00 committed by GitHub
parent 963511bffa
commit a3b27c083f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 417 additions and 2 deletions

1
NEWS
View file

@ -14,6 +14,7 @@ PHP NEWS
- DOM:
. Added Dom\Element::$outerHTML. (nielsdos)
. Added Dom\Element::insertAdjacentHTML(). (nielsdos)
- Output:
. Fixed calculation of aligned buffer size. (cmb)

View file

@ -92,6 +92,9 @@ PHP 8.5 UPGRADE NOTES
attached to a CurlMultiHandle. This includes both handles added using
curl_multi_add_handle() and handles accepted by CURLMOPT_PUSHFUNCTION.
- DOM:
. Added Dom\Element::insertAdjacentHTML().
- PGSQL:
. pg_close_stmt offers an alternative way to close a prepared
statement from the DEALLOCATE sql command in that we can reuse

View file

@ -1715,6 +1715,98 @@ PHP_METHOD(Dom_Element, insertAdjacentText)
}
/* }}} end DOMElement::insertAdjacentText */
/* https://html.spec.whatwg.org/#dom-element-insertadjacenthtml */
PHP_METHOD(Dom_Element, insertAdjacentHTML)
{
zval *where_zv;
zend_string *string;
dom_object *this_intern;
zval *id;
xmlNodePtr thisp;
bool created_context = false;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_OBJECT_OF_CLASS(where_zv, dom_adjacent_position_class_entry)
Z_PARAM_STR(string)
ZEND_PARSE_PARAMETERS_END();
DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, this_intern);
const zend_string *where = Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(where_zv)));
/* 1. We don't do injection sinks. */
/* 2. Let context be NULL */
xmlNodePtr context = NULL;
/* 3. Use the first matching item from this list: (...) */
switch (ZSTR_LEN(where) + ZSTR_VAL(where)[2]) {
case sizeof("BeforeBegin") - 1 + 'f':
case sizeof("AfterEnd") - 1 + 't':
/* 1. Set context to this's parent. */
context = thisp->parent;
/* 2. If context is null or a Document, throw a "NoModificationAllowedError" DOMException. */
if (context == NULL || context->type == XML_DOCUMENT_NODE || context->type == XML_HTML_DOCUMENT_NODE) {
php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, true);
RETURN_THROWS();
}
break;
case sizeof("AfterBegin") - 1 + 't':
case sizeof("BeforeEnd") - 1 + 'f':
/* Set context to this. */
context = thisp;
break;
EMPTY_SWITCH_DEFAULT_CASE();
}
/* 4. If context is not an Element or all of the following are true: (...) */
if (context->type != XML_ELEMENT_NODE
|| (php_dom_ns_is_html_and_document_is_html(context) && xmlStrEqual(context->name, BAD_CAST "html"))) {
/* set context to the result of creating an element given this's node document, body, and the HTML namespace. */
xmlNsPtr html_ns = php_dom_libxml_ns_mapper_ensure_html_ns(php_dom_get_ns_mapper(this_intern));
context = xmlNewDocNode(thisp->doc, html_ns, BAD_CAST "body", NULL);
created_context = true;
if (UNEXPECTED(context == NULL)) {
php_dom_throw_error(INVALID_STATE_ERR, true);
goto err;
}
}
/* 5. Let fragment be the result of invoking the fragment parsing algorithm steps with context and compliantString. */
xmlNodePtr fragment = dom_parse_fragment(this_intern, context, string);
if (fragment == NULL) {
goto err;
}
php_libxml_invalidate_node_list_cache(this_intern->document);
/* 6. Use the first matching item from this list: (...) */
switch (ZSTR_LEN(where) + ZSTR_VAL(where)[2]) {
case sizeof("BeforeBegin") - 1 + 'f':
php_dom_pre_insert(this_intern->document, fragment, thisp->parent, thisp);
break;
case sizeof("AfterEnd") - 1 + 't':
php_dom_pre_insert(this_intern->document, fragment, thisp->parent, thisp->next);
break;
case sizeof("AfterBegin") - 1 + 't':
php_dom_pre_insert(this_intern->document, fragment, thisp, thisp->children);
break;
case sizeof("BeforeEnd") - 1 + 'f':
php_dom_node_append(this_intern->document, fragment, thisp);
break;
EMPTY_SWITCH_DEFAULT_CASE();
}
err:
if (created_context) {
xmlFreeNode(context);
}
}
/* {{{ URL: https://dom.spec.whatwg.org/#dom-element-toggleattribute
Since:
*/

View file

@ -342,7 +342,7 @@ static xmlNodePtr dom_xml_fragment_parsing_algorithm(dom_object *obj, const xmlN
}
/* https://w3c.github.io/DOM-Parsing/#dfn-fragment-parsing-algorithm */
static xmlNodePtr dom_parse_fragment(dom_object *obj, xmlNodePtr context_node, const zend_string *input)
xmlNodePtr dom_parse_fragment(dom_object *obj, xmlNodePtr context_node, const zend_string *input)
{
if (context_node->doc->type == XML_DOCUMENT_NODE) {
return dom_xml_fragment_parsing_algorithm(obj, context_node, input);

View file

@ -211,6 +211,7 @@ void dom_parent_node_query_selector(xmlNodePtr thisp, dom_object *intern, zval *
void dom_parent_node_query_selector_all(xmlNodePtr thisp, dom_object *intern, zval *return_value, const zend_string *selectors_str);
void dom_element_matches(xmlNodePtr thisp, dom_object *intern, zval *return_value, const zend_string *selectors_str);
void dom_element_closest(xmlNodePtr thisp, dom_object *intern, zval *return_value, const zend_string *selectors_str);
xmlNodePtr dom_parse_fragment(dom_object *obj, xmlNodePtr context_node, const zend_string *input);
/* nodemap and nodelist APIs */
xmlNodePtr php_dom_named_node_map_get_named_item(dom_nnodemap_object *objmap, const zend_string *named, bool may_transform);

View file

@ -1632,6 +1632,7 @@ namespace Dom
public function insertAdjacentElement(AdjacentPosition $where, Element $element): ?Element {}
public function insertAdjacentText(AdjacentPosition $where, string $data): void {}
public function insertAdjacentHTML(AdjacentPosition $where, string $string): void {}
/**
* @readonly

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 20c13a727cffb452475989a743ec29a8412a52f1 */
* Stub hash: 860bf40a97ec6570e9f5b0616407ba55d7c8d6f8 */
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_dom_import_simplexml, 0, 1, DOMAttr|DOMElement, 0)
ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0)
@ -785,6 +785,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Dom_Element_insertAdjacent
ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Dom_Element_insertAdjacentHTML, 0, 2, IS_VOID, 0)
ZEND_ARG_OBJ_INFO(0, where, Dom\\AdjacentPosition, 0)
ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Dom_Element_setIdAttribute, 0, 2, IS_VOID, 0)
ZEND_ARG_TYPE_INFO(0, qualifiedName, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, isId, _IS_BOOL, 0)
@ -1275,6 +1280,7 @@ ZEND_METHOD(Dom_Element, getElementsByTagName);
ZEND_METHOD(Dom_Element, getElementsByTagNameNS);
ZEND_METHOD(Dom_Element, insertAdjacentElement);
ZEND_METHOD(Dom_Element, insertAdjacentText);
ZEND_METHOD(Dom_Element, insertAdjacentHTML);
ZEND_METHOD(Dom_Element, setIdAttributeNode);
ZEND_METHOD(Dom_Element, querySelector);
ZEND_METHOD(Dom_Element, querySelectorAll);
@ -1649,6 +1655,7 @@ static const zend_function_entry class_Dom_Element_methods[] = {
ZEND_ME(Dom_Element, getElementsByTagNameNS, arginfo_class_Dom_Element_getElementsByTagNameNS, ZEND_ACC_PUBLIC)
ZEND_ME(Dom_Element, insertAdjacentElement, arginfo_class_Dom_Element_insertAdjacentElement, ZEND_ACC_PUBLIC)
ZEND_ME(Dom_Element, insertAdjacentText, arginfo_class_Dom_Element_insertAdjacentText, ZEND_ACC_PUBLIC)
ZEND_ME(Dom_Element, insertAdjacentHTML, arginfo_class_Dom_Element_insertAdjacentHTML, ZEND_ACC_PUBLIC)
ZEND_RAW_FENTRY("setIdAttribute", zim_DOMElement_setIdAttribute, arginfo_class_Dom_Element_setIdAttribute, ZEND_ACC_PUBLIC, NULL, NULL)
ZEND_RAW_FENTRY("setIdAttributeNS", zim_DOMElement_setIdAttributeNS, arginfo_class_Dom_Element_setIdAttributeNS, ZEND_ACC_PUBLIC, NULL, NULL)
ZEND_ME(Dom_Element, setIdAttributeNode, arginfo_class_Dom_Element_setIdAttributeNode, ZEND_ACC_PUBLIC)

View file

@ -0,0 +1,114 @@
--TEST--
Dom\Element::insertAdjacentHTML() with HTML nodes
--EXTENSIONS--
dom
--FILE--
<?php
const POSITIONS = [
Dom\AdjacentPosition::BeforeBegin,
Dom\AdjacentPosition::AfterBegin,
Dom\AdjacentPosition::BeforeEnd,
Dom\AdjacentPosition::AfterEnd,
];
function test(string $html) {
echo "=== HTML ($html) ===\n";
foreach (POSITIONS as $position) {
echo "--- Position ", $position->name, " ---\n";
$dom = Dom\HTMLDocument::createFromString("<div></div>", LIBXML_NOERROR);
$div = $dom->body->firstChild;
$div->append("Sample text");
$div->insertAdjacentHTML($position, $html);
echo $dom->saveXML(), "\n";
echo $dom->saveHTML(), "\n";
var_dump($div->childNodes->length);
var_dump($dom->body->childNodes->length);
}
}
test("<p>foo</p><p>bar</p>");
test("text");
test("");
?>
--EXPECT--
=== HTML (<p>foo</p><p>bar</p>) ===
--- Position BeforeBegin ---
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><p>foo</p><p>bar</p><div>Sample text</div></body></html>
<html><head></head><body><p>foo</p><p>bar</p><div>Sample text</div></body></html>
int(1)
int(3)
--- Position AfterBegin ---
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div><p>foo</p><p>bar</p>Sample text</div></body></html>
<html><head></head><body><div><p>foo</p><p>bar</p>Sample text</div></body></html>
int(3)
int(1)
--- Position BeforeEnd ---
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample text<p>foo</p><p>bar</p></div></body></html>
<html><head></head><body><div>Sample text<p>foo</p><p>bar</p></div></body></html>
int(3)
int(1)
--- Position AfterEnd ---
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample text</div><p>foo</p><p>bar</p></body></html>
<html><head></head><body><div>Sample text</div><p>foo</p><p>bar</p></body></html>
int(1)
int(3)
=== HTML (text) ===
--- Position BeforeBegin ---
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>text<div>Sample text</div></body></html>
<html><head></head><body>text<div>Sample text</div></body></html>
int(1)
int(2)
--- Position AfterBegin ---
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>textSample text</div></body></html>
<html><head></head><body><div>textSample text</div></body></html>
int(2)
int(1)
--- Position BeforeEnd ---
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample texttext</div></body></html>
<html><head></head><body><div>Sample texttext</div></body></html>
int(2)
int(1)
--- Position AfterEnd ---
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample text</div>text</body></html>
<html><head></head><body><div>Sample text</div>text</body></html>
int(1)
int(2)
=== HTML () ===
--- Position BeforeBegin ---
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample text</div></body></html>
<html><head></head><body><div>Sample text</div></body></html>
int(1)
int(1)
--- Position AfterBegin ---
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample text</div></body></html>
<html><head></head><body><div>Sample text</div></body></html>
int(1)
int(1)
--- Position BeforeEnd ---
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample text</div></body></html>
<html><head></head><body><div>Sample text</div></body></html>
int(1)
int(1)
--- Position AfterEnd ---
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample text</div></body></html>
<html><head></head><body><div>Sample text</div></body></html>
int(1)
int(1)

View file

@ -0,0 +1,24 @@
--TEST--
Dom\Element::insertAdjacentHTML() with HTML nodes - edge case
--EXTENSIONS--
dom
--FILE--
<?php
$dom = Dom\HTMLDocument::createFromString("", LIBXML_NOERROR);
$fragment = $dom->createDocumentFragment();
$node = $fragment->appendChild($dom->createElement("node"));
$node->insertAdjacentHTML(Dom\AdjacentPosition::BeforeBegin, "<p>foo</p>");
echo $dom->saveHtml($fragment), "\n";
$dom->firstChild->insertAdjacentHTML(Dom\AdjacentPosition::AfterBegin, $node->outerHTML);
echo $dom->saveHtml(), "\n";
?>
--EXPECT--
<p>foo</p><node></node>
<html><node></node><head></head><body></body></html>

View file

@ -0,0 +1,54 @@
--TEST--
Dom\Element::insertAdjacentHTML() with HTML nodes - error conditions
--EXTENSIONS--
dom
--FILE--
<?php
$dom = Dom\HTMLDocument::createEmpty();
$element = $dom->createElement('root');
echo "--- BeforeBegin no parent ---\n";
try {
$element->insertAdjacentHTML(Dom\AdjacentPosition::BeforeBegin, "test");
} catch (DOMException $e) {
echo $e->getMessage(), "\n";
}
echo "--- AfterEnd no parent ---\n";
try {
$element->insertAdjacentHTML(Dom\AdjacentPosition::AfterEnd, "test");
} catch (DOMException $e) {
echo $e->getMessage(), "\n";
}
$dom->appendChild($element);
echo "--- BeforeBegin document parent ---\n";
try {
$element->insertAdjacentHTML(Dom\AdjacentPosition::BeforeBegin, "test");
} catch (DOMException $e) {
echo $e->getMessage(), "\n";
}
echo "--- AfterEnd document parent ---\n";
try {
$element->insertAdjacentHTML(Dom\AdjacentPosition::AfterEnd, "test");
} catch (DOMException $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
--- BeforeBegin no parent ---
No Modification Allowed Error
--- AfterEnd no parent ---
No Modification Allowed Error
--- BeforeBegin document parent ---
No Modification Allowed Error
--- AfterEnd document parent ---
No Modification Allowed Error

View file

@ -0,0 +1,101 @@
--TEST--
Dom\Element::insertAdjacentHTML() with XML nodes
--EXTENSIONS--
dom
--FILE--
<?php
const POSITIONS = [
Dom\AdjacentPosition::BeforeBegin,
Dom\AdjacentPosition::AfterBegin,
Dom\AdjacentPosition::BeforeEnd,
Dom\AdjacentPosition::AfterEnd,
];
function test(string $xml) {
echo "=== XML ($xml) ===\n";
foreach (POSITIONS as $position) {
echo "--- Position ", $position->name, " ---\n";
$dom = Dom\XMLDocument::createFromString('<root xmlns:x="urn:x"><div/></root>');
$div = $dom->documentElement->firstChild;
$div->append("Sample text");
$div->insertAdjacentHTML($position, $xml);
echo $dom->saveXML(), "\n";
var_dump($div->childNodes->length);
var_dump($dom->documentElement->childNodes->length);
}
}
test('<x:x><y xmlns:x="urn:x2"/></x:x><nons/>');
test('<?pi node?><!-- comment -->&amp;');
test('text node');
?>
--EXPECT--
=== XML (<x:x><y xmlns:x="urn:x2"/></x:x><nons/>) ===
--- Position BeforeBegin ---
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:x="urn:x"><x:x><y xmlns:x="urn:x2"/></x:x><nons/><div>Sample text</div></root>
int(1)
int(3)
--- Position AfterBegin ---
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:x="urn:x"><div><x:x><y xmlns:x="urn:x2"/></x:x><nons/>Sample text</div></root>
int(3)
int(1)
--- Position BeforeEnd ---
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:x="urn:x"><div>Sample text<x:x><y xmlns:x="urn:x2"/></x:x><nons/></div></root>
int(3)
int(1)
--- Position AfterEnd ---
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:x="urn:x"><div>Sample text</div><x:x><y xmlns:x="urn:x2"/></x:x><nons/></root>
int(1)
int(3)
=== XML (<?pi node?><!-- comment -->&amp;) ===
--- Position BeforeBegin ---
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:x="urn:x"><?pi node?><!-- comment -->&amp;<div>Sample text</div></root>
int(1)
int(4)
--- Position AfterBegin ---
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:x="urn:x"><div><?pi node?><!-- comment -->&amp;Sample text</div></root>
int(4)
int(1)
--- Position BeforeEnd ---
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:x="urn:x"><div>Sample text<?pi node?><!-- comment -->&amp;</div></root>
int(4)
int(1)
--- Position AfterEnd ---
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:x="urn:x"><div>Sample text</div><?pi node?><!-- comment -->&amp;</root>
int(1)
int(4)
=== XML (text node) ===
--- Position BeforeBegin ---
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:x="urn:x">text node<div>Sample text</div></root>
int(1)
int(2)
--- Position AfterBegin ---
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:x="urn:x"><div>text nodeSample text</div></root>
int(2)
int(1)
--- Position BeforeEnd ---
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:x="urn:x"><div>Sample texttext node</div></root>
int(2)
int(1)
--- Position AfterEnd ---
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:x="urn:x"><div>Sample text</div>text node</root>
int(1)
int(2)

View file

@ -0,0 +1,17 @@
--TEST--
Dom\Element::insertAdjacentHTML() with XML nodes - errors
--EXTENSIONS--
dom
--FILE--
<?php
$dom = Dom\XMLDocument::createFromString('<root/>');
try {
$dom->documentElement->insertAdjacentHTML(Dom\AdjacentPosition::AfterBegin, "<non-well-formed>");
} catch (DOMException $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
XML fragment is not well-formed