Skip to content

Commit a3b27c0

Browse files
authored
Add Dom\Element::insertAdjacentHTML() (#16614)
1 parent 963511b commit a3b27c0

12 files changed

+417
-2
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ PHP NEWS
1414

1515
- DOM:
1616
. Added Dom\Element::$outerHTML. (nielsdos)
17+
. Added Dom\Element::insertAdjacentHTML(). (nielsdos)
1718

1819
- Output:
1920
. Fixed calculation of aligned buffer size. (cmb)

UPGRADING

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ PHP 8.5 UPGRADE NOTES
9292
attached to a CurlMultiHandle. This includes both handles added using
9393
curl_multi_add_handle() and handles accepted by CURLMOPT_PUSHFUNCTION.
9494

95+
- DOM:
96+
. Added Dom\Element::insertAdjacentHTML().
97+
9598
- PGSQL:
9699
. pg_close_stmt offers an alternative way to close a prepared
97100
statement from the DEALLOCATE sql command in that we can reuse

ext/dom/element.c

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,6 +1715,98 @@ PHP_METHOD(Dom_Element, insertAdjacentText)
17151715
}
17161716
/* }}} end DOMElement::insertAdjacentText */
17171717

1718+
/* https://html.spec.whatwg.org/#dom-element-insertadjacenthtml */
1719+
PHP_METHOD(Dom_Element, insertAdjacentHTML)
1720+
{
1721+
zval *where_zv;
1722+
zend_string *string;
1723+
1724+
dom_object *this_intern;
1725+
zval *id;
1726+
xmlNodePtr thisp;
1727+
1728+
bool created_context = false;
1729+
1730+
ZEND_PARSE_PARAMETERS_START(2, 2)
1731+
Z_PARAM_OBJECT_OF_CLASS(where_zv, dom_adjacent_position_class_entry)
1732+
Z_PARAM_STR(string)
1733+
ZEND_PARSE_PARAMETERS_END();
1734+
1735+
DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, this_intern);
1736+
1737+
const zend_string *where = Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(where_zv)));
1738+
1739+
/* 1. We don't do injection sinks. */
1740+
1741+
/* 2. Let context be NULL */
1742+
xmlNodePtr context = NULL;
1743+
1744+
/* 3. Use the first matching item from this list: (...) */
1745+
switch (ZSTR_LEN(where) + ZSTR_VAL(where)[2]) {
1746+
case sizeof("BeforeBegin") - 1 + 'f':
1747+
case sizeof("AfterEnd") - 1 + 't':
1748+
/* 1. Set context to this's parent. */
1749+
context = thisp->parent;
1750+
1751+
/* 2. If context is null or a Document, throw a "NoModificationAllowedError" DOMException. */
1752+
if (context == NULL || context->type == XML_DOCUMENT_NODE || context->type == XML_HTML_DOCUMENT_NODE) {
1753+
php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, true);
1754+
RETURN_THROWS();
1755+
}
1756+
break;
1757+
case sizeof("AfterBegin") - 1 + 't':
1758+
case sizeof("BeforeEnd") - 1 + 'f':
1759+
/* Set context to this. */
1760+
context = thisp;
1761+
break;
1762+
EMPTY_SWITCH_DEFAULT_CASE();
1763+
}
1764+
1765+
/* 4. If context is not an Element or all of the following are true: (...) */
1766+
if (context->type != XML_ELEMENT_NODE
1767+
|| (php_dom_ns_is_html_and_document_is_html(context) && xmlStrEqual(context->name, BAD_CAST "html"))) {
1768+
/* set context to the result of creating an element given this's node document, body, and the HTML namespace. */
1769+
xmlNsPtr html_ns = php_dom_libxml_ns_mapper_ensure_html_ns(php_dom_get_ns_mapper(this_intern));
1770+
1771+
context = xmlNewDocNode(thisp->doc, html_ns, BAD_CAST "body", NULL);
1772+
created_context = true;
1773+
if (UNEXPECTED(context == NULL)) {
1774+
php_dom_throw_error(INVALID_STATE_ERR, true);
1775+
goto err;
1776+
}
1777+
}
1778+
1779+
/* 5. Let fragment be the result of invoking the fragment parsing algorithm steps with context and compliantString. */
1780+
xmlNodePtr fragment = dom_parse_fragment(this_intern, context, string);
1781+
if (fragment == NULL) {
1782+
goto err;
1783+
}
1784+
1785+
php_libxml_invalidate_node_list_cache(this_intern->document);
1786+
1787+
/* 6. Use the first matching item from this list: (...) */
1788+
switch (ZSTR_LEN(where) + ZSTR_VAL(where)[2]) {
1789+
case sizeof("BeforeBegin") - 1 + 'f':
1790+
php_dom_pre_insert(this_intern->document, fragment, thisp->parent, thisp);
1791+
break;
1792+
case sizeof("AfterEnd") - 1 + 't':
1793+
php_dom_pre_insert(this_intern->document, fragment, thisp->parent, thisp->next);
1794+
break;
1795+
case sizeof("AfterBegin") - 1 + 't':
1796+
php_dom_pre_insert(this_intern->document, fragment, thisp, thisp->children);
1797+
break;
1798+
case sizeof("BeforeEnd") - 1 + 'f':
1799+
php_dom_node_append(this_intern->document, fragment, thisp);
1800+
break;
1801+
EMPTY_SWITCH_DEFAULT_CASE();
1802+
}
1803+
1804+
err:
1805+
if (created_context) {
1806+
xmlFreeNode(context);
1807+
}
1808+
}
1809+
17181810
/* {{{ URL: https://dom.spec.whatwg.org/#dom-element-toggleattribute
17191811
Since:
17201812
*/

ext/dom/inner_outer_html_mixin.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ static xmlNodePtr dom_xml_fragment_parsing_algorithm(dom_object *obj, const xmlN
342342
}
343343

344344
/* https://w3c.github.io/DOM-Parsing/#dfn-fragment-parsing-algorithm */
345-
static xmlNodePtr dom_parse_fragment(dom_object *obj, xmlNodePtr context_node, const zend_string *input)
345+
xmlNodePtr dom_parse_fragment(dom_object *obj, xmlNodePtr context_node, const zend_string *input)
346346
{
347347
if (context_node->doc->type == XML_DOCUMENT_NODE) {
348348
return dom_xml_fragment_parsing_algorithm(obj, context_node, input);

ext/dom/php_dom.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ void dom_parent_node_query_selector(xmlNodePtr thisp, dom_object *intern, zval *
211211
void dom_parent_node_query_selector_all(xmlNodePtr thisp, dom_object *intern, zval *return_value, const zend_string *selectors_str);
212212
void dom_element_matches(xmlNodePtr thisp, dom_object *intern, zval *return_value, const zend_string *selectors_str);
213213
void dom_element_closest(xmlNodePtr thisp, dom_object *intern, zval *return_value, const zend_string *selectors_str);
214+
xmlNodePtr dom_parse_fragment(dom_object *obj, xmlNodePtr context_node, const zend_string *input);
214215

215216
/* nodemap and nodelist APIs */
216217
xmlNodePtr php_dom_named_node_map_get_named_item(dom_nnodemap_object *objmap, const zend_string *named, bool may_transform);

ext/dom/php_dom.stub.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,6 +1632,7 @@ public function getElementsByTagNameNS(?string $namespace, string $localName): H
16321632

16331633
public function insertAdjacentElement(AdjacentPosition $where, Element $element): ?Element {}
16341634
public function insertAdjacentText(AdjacentPosition $where, string $data): void {}
1635+
public function insertAdjacentHTML(AdjacentPosition $where, string $string): void {}
16351636

16361637
/**
16371638
* @readonly

ext/dom/php_dom_arginfo.h

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
--TEST--
2+
Dom\Element::insertAdjacentHTML() with HTML nodes
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
const POSITIONS = [
9+
Dom\AdjacentPosition::BeforeBegin,
10+
Dom\AdjacentPosition::AfterBegin,
11+
Dom\AdjacentPosition::BeforeEnd,
12+
Dom\AdjacentPosition::AfterEnd,
13+
];
14+
15+
function test(string $html) {
16+
echo "=== HTML ($html) ===\n";
17+
18+
foreach (POSITIONS as $position) {
19+
echo "--- Position ", $position->name, " ---\n";
20+
21+
$dom = Dom\HTMLDocument::createFromString("<div></div>", LIBXML_NOERROR);
22+
$div = $dom->body->firstChild;
23+
$div->append("Sample text");
24+
25+
$div->insertAdjacentHTML($position, $html);
26+
27+
echo $dom->saveXML(), "\n";
28+
echo $dom->saveHTML(), "\n";
29+
var_dump($div->childNodes->length);
30+
var_dump($dom->body->childNodes->length);
31+
}
32+
}
33+
34+
test("<p>foo</p><p>bar</p>");
35+
test("text");
36+
test("");
37+
38+
?>
39+
--EXPECT--
40+
=== HTML (<p>foo</p><p>bar</p>) ===
41+
--- Position BeforeBegin ---
42+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
43+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><p>foo</p><p>bar</p><div>Sample text</div></body></html>
44+
<html><head></head><body><p>foo</p><p>bar</p><div>Sample text</div></body></html>
45+
int(1)
46+
int(3)
47+
--- Position AfterBegin ---
48+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
49+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div><p>foo</p><p>bar</p>Sample text</div></body></html>
50+
<html><head></head><body><div><p>foo</p><p>bar</p>Sample text</div></body></html>
51+
int(3)
52+
int(1)
53+
--- Position BeforeEnd ---
54+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
55+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample text<p>foo</p><p>bar</p></div></body></html>
56+
<html><head></head><body><div>Sample text<p>foo</p><p>bar</p></div></body></html>
57+
int(3)
58+
int(1)
59+
--- Position AfterEnd ---
60+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
61+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample text</div><p>foo</p><p>bar</p></body></html>
62+
<html><head></head><body><div>Sample text</div><p>foo</p><p>bar</p></body></html>
63+
int(1)
64+
int(3)
65+
=== HTML (text) ===
66+
--- Position BeforeBegin ---
67+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
68+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>text<div>Sample text</div></body></html>
69+
<html><head></head><body>text<div>Sample text</div></body></html>
70+
int(1)
71+
int(2)
72+
--- Position AfterBegin ---
73+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
74+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>textSample text</div></body></html>
75+
<html><head></head><body><div>textSample text</div></body></html>
76+
int(2)
77+
int(1)
78+
--- Position BeforeEnd ---
79+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
80+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample texttext</div></body></html>
81+
<html><head></head><body><div>Sample texttext</div></body></html>
82+
int(2)
83+
int(1)
84+
--- Position AfterEnd ---
85+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
86+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample text</div>text</body></html>
87+
<html><head></head><body><div>Sample text</div>text</body></html>
88+
int(1)
89+
int(2)
90+
=== HTML () ===
91+
--- Position BeforeBegin ---
92+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
93+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample text</div></body></html>
94+
<html><head></head><body><div>Sample text</div></body></html>
95+
int(1)
96+
int(1)
97+
--- Position AfterBegin ---
98+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
99+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample text</div></body></html>
100+
<html><head></head><body><div>Sample text</div></body></html>
101+
int(1)
102+
int(1)
103+
--- Position BeforeEnd ---
104+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
105+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample text</div></body></html>
106+
<html><head></head><body><div>Sample text</div></body></html>
107+
int(1)
108+
int(1)
109+
--- Position AfterEnd ---
110+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
111+
<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body><div>Sample text</div></body></html>
112+
<html><head></head><body><div>Sample text</div></body></html>
113+
int(1)
114+
int(1)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Dom\Element::insertAdjacentHTML() with HTML nodes - edge case
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = Dom\HTMLDocument::createFromString("", LIBXML_NOERROR);
9+
10+
$fragment = $dom->createDocumentFragment();
11+
$node = $fragment->appendChild($dom->createElement("node"));
12+
13+
$node->insertAdjacentHTML(Dom\AdjacentPosition::BeforeBegin, "<p>foo</p>");
14+
15+
echo $dom->saveHtml($fragment), "\n";
16+
17+
$dom->firstChild->insertAdjacentHTML(Dom\AdjacentPosition::AfterBegin, $node->outerHTML);
18+
19+
echo $dom->saveHtml(), "\n";
20+
21+
?>
22+
--EXPECT--
23+
<p>foo</p><node></node>
24+
<html><node></node><head></head><body></body></html>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
--TEST--
2+
Dom\Element::insertAdjacentHTML() with HTML nodes - error conditions
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = Dom\HTMLDocument::createEmpty();
9+
$element = $dom->createElement('root');
10+
11+
echo "--- BeforeBegin no parent ---\n";
12+
13+
try {
14+
$element->insertAdjacentHTML(Dom\AdjacentPosition::BeforeBegin, "test");
15+
} catch (DOMException $e) {
16+
echo $e->getMessage(), "\n";
17+
}
18+
19+
echo "--- AfterEnd no parent ---\n";
20+
21+
try {
22+
$element->insertAdjacentHTML(Dom\AdjacentPosition::AfterEnd, "test");
23+
} catch (DOMException $e) {
24+
echo $e->getMessage(), "\n";
25+
}
26+
27+
$dom->appendChild($element);
28+
29+
echo "--- BeforeBegin document parent ---\n";
30+
31+
try {
32+
$element->insertAdjacentHTML(Dom\AdjacentPosition::BeforeBegin, "test");
33+
} catch (DOMException $e) {
34+
echo $e->getMessage(), "\n";
35+
}
36+
37+
echo "--- AfterEnd document parent ---\n";
38+
39+
try {
40+
$element->insertAdjacentHTML(Dom\AdjacentPosition::AfterEnd, "test");
41+
} catch (DOMException $e) {
42+
echo $e->getMessage(), "\n";
43+
}
44+
45+
?>
46+
--EXPECT--
47+
--- BeforeBegin no parent ---
48+
No Modification Allowed Error
49+
--- AfterEnd no parent ---
50+
No Modification Allowed Error
51+
--- BeforeBegin document parent ---
52+
No Modification Allowed Error
53+
--- AfterEnd document parent ---
54+
No Modification Allowed Error

0 commit comments

Comments
 (0)