", LIBXML_NOERROR);
+var_dump($dom->body?->nodeName);
+
+echo "--- After body removal ---\n";
+
+$dom->body->remove();
+var_dump($dom->body?->nodeName);
+
+echo "--- body in no namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("", "body"));
+var_dump($dom->body?->nodeName);
+$tmp->remove();
+
+echo "--- frameset in no namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("", "frameset"));
+var_dump($dom->body?->nodeName);
+$tmp->remove();
+
+echo "--- body in right namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "body"));
+var_dump($dom->body?->nodeName);
+$tmp->remove();
+
+echo "--- frameset in right namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "frameset"));
+var_dump($dom->body?->nodeName);
+$tmp->remove();
+
+echo "--- prefixed body in right namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix:body"));
+var_dump($dom->body?->nodeName);
+$tmp->remove();
+
+echo "--- prefixed frameset in right namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix:frameset"));
+var_dump($dom->body?->nodeName);
+$tmp->remove();
+
+echo "--- multiple body-like elements in right namespace ---\n";
+
+$tmp1 = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix1:body"));
+var_dump($dom->body?->nodeName);
+$tmp2 = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix2:frameset"));
+var_dump($dom->body?->nodeName);
+$tmp1->remove();
+var_dump($dom->body?->nodeName);
+$tmp2->remove();
+var_dump($dom->body?->nodeName);
+
+echo "--- html element in no namespace ---\n";
+
+$dom = Dom\XMLDocument::createFromString(<<
+
+
+XML);
+var_dump($dom->body);
+
+?>
+--EXPECT--
+--- From parsing ---
+string(4) "BODY"
+--- After body removal ---
+NULL
+--- body in no namespace ---
+NULL
+--- frameset in no namespace ---
+NULL
+--- body in right namespace ---
+string(4) "BODY"
+--- frameset in right namespace ---
+string(8) "FRAMESET"
+--- prefixed body in right namespace ---
+string(11) "PREFIX:BODY"
+--- prefixed frameset in right namespace ---
+string(15) "PREFIX:FRAMESET"
+--- multiple body-like elements in right namespace ---
+string(12) "PREFIX1:BODY"
+string(12) "PREFIX1:BODY"
+string(16) "PREFIX2:FRAMESET"
+NULL
+--- html element in no namespace ---
+NULL
diff --git a/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_01.phpt b/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_01.phpt
index 2931aebab28f5..a82d882cd366c 100644
--- a/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_01.phpt
+++ b/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_01.phpt
@@ -23,7 +23,7 @@ var_dump(get_class($dom->getElementsByTagName("p")->item(0)));
?>
--EXPECT--
-object(Dom\HTMLDocument)#1 (25) {
+object(Dom\HTMLDocument)#1 (26) {
["implementation"]=>
string(22) "(object value omitted)"
["URL"]=>
@@ -46,6 +46,8 @@ object(Dom\HTMLDocument)#1 (25) {
string(22) "(object value omitted)"
["childElementCount"]=>
int(1)
+ ["body"]=>
+ string(22) "(object value omitted)"
["nodeType"]=>
int(13)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_02.phpt b/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_02.phpt
index c356cf7ba9215..67565b13fbc32 100644
--- a/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_02.phpt
+++ b/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_02.phpt
@@ -23,7 +23,7 @@ var_dump(get_class($dom->getElementsByTagName("p")->item(0)));
?>
--EXPECT--
-object(Dom\HTMLDocument)#1 (25) {
+object(Dom\HTMLDocument)#1 (26) {
["implementation"]=>
string(22) "(object value omitted)"
["URL"]=>
@@ -46,6 +46,8 @@ object(Dom\HTMLDocument)#1 (25) {
string(22) "(object value omitted)"
["childElementCount"]=>
int(1)
+ ["body"]=>
+ string(22) "(object value omitted)"
["nodeType"]=>
int(13)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/spec/Document_implementation_createDocument.phpt b/ext/dom/tests/modern/spec/Document_implementation_createDocument.phpt
index 5384cf6331f76..57313e4260247 100644
--- a/ext/dom/tests/modern/spec/Document_implementation_createDocument.phpt
+++ b/ext/dom/tests/modern/spec/Document_implementation_createDocument.phpt
@@ -37,7 +37,7 @@ echo $dom->implementation->createDocument(null, "", $dtd)->saveXml(), "\n";
?>
--EXPECT--
--- (null, "") ---
-object(Dom\XMLDocument)#3 (29) {
+object(Dom\XMLDocument)#3 (30) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@@ -68,6 +68,8 @@ object(Dom\XMLDocument)#3 (29) {
NULL
["childElementCount"]=>
int(0)
+ ["body"]=>
+ NULL
["nodeType"]=>
int(9)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/xml/XMLDocument_debug.phpt b/ext/dom/tests/modern/xml/XMLDocument_debug.phpt
index c9ae2afa95fdf..2d09e93e147eb 100644
--- a/ext/dom/tests/modern/xml/XMLDocument_debug.phpt
+++ b/ext/dom/tests/modern/xml/XMLDocument_debug.phpt
@@ -10,7 +10,7 @@ var_dump($dom);
?>
--EXPECT--
-object(Dom\XMLDocument)#1 (29) {
+object(Dom\XMLDocument)#1 (30) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@@ -41,6 +41,8 @@ object(Dom\XMLDocument)#1 (29) {
NULL
["childElementCount"]=>
int(0)
+ ["body"]=>
+ NULL
["nodeType"]=>
int(9)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/xml/XMLDocument_fromEmptyDocument_02.phpt b/ext/dom/tests/modern/xml/XMLDocument_fromEmptyDocument_02.phpt
index a804394bc8441..8b8fd9d2003d6 100644
--- a/ext/dom/tests/modern/xml/XMLDocument_fromEmptyDocument_02.phpt
+++ b/ext/dom/tests/modern/xml/XMLDocument_fromEmptyDocument_02.phpt
@@ -10,7 +10,7 @@ var_dump($dom);
?>
--EXPECT--
-object(Dom\XMLDocument)#1 (29) {
+object(Dom\XMLDocument)#1 (30) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@@ -41,6 +41,8 @@ object(Dom\XMLDocument)#1 (29) {
NULL
["childElementCount"]=>
int(0)
+ ["body"]=>
+ NULL
["nodeType"]=>
int(9)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/xml/XMLDocument_node_ownerDocument_for_XML.phpt b/ext/dom/tests/modern/xml/XMLDocument_node_ownerDocument_for_XML.phpt
index 946f9495114a7..ee2d5b0116b95 100644
--- a/ext/dom/tests/modern/xml/XMLDocument_node_ownerDocument_for_XML.phpt
+++ b/ext/dom/tests/modern/xml/XMLDocument_node_ownerDocument_for_XML.phpt
@@ -13,7 +13,7 @@ var_dump($element->ownerDocument);
?>
--EXPECTF--
-object(Dom\XMLDocument)#1 (29) {
+object(Dom\XMLDocument)#1 (30) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@@ -44,6 +44,8 @@ object(Dom\XMLDocument)#1 (29) {
string(22) "(object value omitted)"
["childElementCount"]=>
int(1)
+ ["body"]=>
+ NULL
["nodeType"]=>
int(9)
["nodeName"]=>
From 4fc905fb9b85e89d70e8b7a860205c6f75f33141 Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Sat, 23 Mar 2024 14:02:49 +0100
Subject: [PATCH 2/7] Implement Dom\Document::$head
---
ext/dom/dom_properties.h | 1 +
ext/dom/html_document.c | 28 ++++++-
ext/dom/php_dom.c | 1 +
ext/dom/php_dom.stub.php | 2 +
ext/dom/php_dom_arginfo.h | 9 ++-
.../html/interactions/Document_head.phpt | 74 +++++++++++++++++++
...should_retain_properties_and_owner_01.phpt | 4 +-
...should_retain_properties_and_owner_02.phpt | 4 +-
...ocument_implementation_createDocument.phpt | 4 +-
.../tests/modern/xml/XMLDocument_debug.phpt | 4 +-
.../xml/XMLDocument_fromEmptyDocument_02.phpt | 4 +-
...MLDocument_node_ownerDocument_for_XML.phpt | 4 +-
12 files changed, 128 insertions(+), 11 deletions(-)
create mode 100644 ext/dom/tests/modern/html/interactions/Document_head.phpt
diff --git a/ext/dom/dom_properties.h b/ext/dom/dom_properties.h
index d5da955ebfead..5ab6fedfe59ea 100644
--- a/ext/dom/dom_properties.h
+++ b/ext/dom/dom_properties.h
@@ -63,6 +63,7 @@ zend_result dom_document_substitue_entities_write(dom_object *obj, zval *newval)
/* html5 document properties */
zend_result dom_html_document_encoding_write(dom_object *obj, zval *retval);
zend_result dom_html_document_body_read(dom_object *obj, zval *retval);
+zend_result dom_html_document_head_read(dom_object *obj, zval *retval);
/* documenttype properties */
zend_result dom_documenttype_name_read(dom_object *obj, zval *retval);
diff --git a/ext/dom/html_document.c b/ext/dom/html_document.c
index 74a1800bbdf57..5cfb75fecdd79 100644
--- a/ext/dom/html_document.c
+++ b/ext/dom/html_document.c
@@ -1358,8 +1358,7 @@ zend_result dom_html_document_encoding_write(dom_object *obj, zval *newval)
return SUCCESS;
}
-/* https://html.spec.whatwg.org/#dom-document-body */
-zend_result dom_html_document_body_read(dom_object *obj, zval *retval)
+zend_result dom_html_document_element_read_helper(dom_object *obj, zval *retval, bool (*accept)(const xmlChar *))
{
DOM_PROP_NODE(const xmlDoc *, docp, obj);
@@ -1371,8 +1370,7 @@ zend_result dom_html_document_body_read(dom_object *obj, zval *retval)
xmlNodePtr cur = root->children;
while (cur != NULL) {
- if (cur->type == XML_ELEMENT_NODE && php_dom_ns_is_fast(cur, php_dom_ns_is_html_magic_token)
- && (xmlStrEqual(cur->name, BAD_CAST "body") || xmlStrEqual(cur->name, BAD_CAST "frameset"))) {
+ if (cur->type == XML_ELEMENT_NODE && php_dom_ns_is_fast(cur, php_dom_ns_is_html_magic_token) && accept(cur->name)) {
php_dom_create_object(cur, retval, obj);
return SUCCESS;
}
@@ -1383,4 +1381,26 @@ zend_result dom_html_document_body_read(dom_object *obj, zval *retval)
return SUCCESS;
}
+static bool dom_accept_body_name(const xmlChar *name)
+{
+ return xmlStrEqual(name, BAD_CAST "body") || xmlStrEqual(name, BAD_CAST "frameset");
+}
+
+static bool dom_accept_head_name(const xmlChar *name)
+{
+ return xmlStrEqual(name, BAD_CAST "head");
+}
+
+/* https://html.spec.whatwg.org/#dom-document-body */
+zend_result dom_html_document_body_read(dom_object *obj, zval *retval)
+{
+ return dom_html_document_element_read_helper(obj, retval, dom_accept_body_name);
+}
+
+/* https://html.spec.whatwg.org/#dom-document-head */
+zend_result dom_html_document_head_read(dom_object *obj, zval *retval)
+{
+ return dom_html_document_element_read_helper(obj, retval, dom_accept_head_name);
+}
+
#endif /* HAVE_LIBXML && HAVE_DOM */
diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c
index 7d0fda2918ded..b08dc935a9a01 100644
--- a/ext/dom/php_dom.c
+++ b/ext/dom/php_dom.c
@@ -852,6 +852,7 @@ PHP_MINIT_FUNCTION(dom)
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "body", dom_html_document_body_read, NULL);
+ DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "head", dom_html_document_head_read, NULL);
zend_hash_merge(&dom_abstract_base_document_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
/* No need to register in &classes because this is an abstract class handler. */
diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php
index 34525d2158e1f..a21b818b89994 100644
--- a/ext/dom/php_dom.stub.php
+++ b/ext/dom/php_dom.stub.php
@@ -1582,6 +1582,8 @@ public function replaceChildren(Node|string ...$nodes): void {}
public function importLegacyNode(\DOMNode $node, bool $deep = false): Node {}
public ?Element $body;
+ /** @readonly */
+ public ?Element $head;
}
final class HTMLDocument extends Document
diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h
index ce7188d39521f..b364952687e7f 100644
--- a/ext/dom/php_dom_arginfo.h
+++ b/ext/dom/php_dom_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 4d7d3a428304aa0544da0b1b57caa4e5948fa31b */
+ * Stub hash: 0795d4e52f62ab33df92cfbdd5178223fbfc3eeb */
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)
@@ -3449,6 +3449,13 @@ static zend_class_entry *register_class_Dom_Document(zend_class_entry *class_ent
zend_declare_typed_property(class_entry, property_body_name, &property_body_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_body_class_Dom_Element, 0, MAY_BE_NULL));
zend_string_release(property_body_name);
+ zval property_head_default_value;
+ ZVAL_UNDEF(&property_head_default_value);
+ zend_string *property_head_name = zend_string_init("head", sizeof("head") - 1, 1);
+ zend_string *property_head_class_Dom_Element = zend_string_init("Dom\\Element", sizeof("Dom\\Element")-1, 1);
+ zend_declare_typed_property(class_entry, property_head_name, &property_head_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_head_class_Dom_Element, 0, MAY_BE_NULL));
+ zend_string_release(property_head_name);
+
return class_entry;
}
diff --git a/ext/dom/tests/modern/html/interactions/Document_head.phpt b/ext/dom/tests/modern/html/interactions/Document_head.phpt
new file mode 100644
index 0000000000000..cc34945951405
--- /dev/null
+++ b/ext/dom/tests/modern/html/interactions/Document_head.phpt
@@ -0,0 +1,74 @@
+--TEST--
+Test Dom\Document::$head
+--EXTENSIONS--
+dom
+--FILE--
+foo", LIBXML_NOERROR);
+var_dump($dom->head?->nodeName);
+
+echo "--- After head removal ---\n";
+
+$dom->head->remove();
+var_dump($dom->head?->nodeName);
+
+echo "--- head in no namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("", "head"));
+var_dump($dom->head?->nodeName);
+$tmp->remove();
+
+echo "--- head in right namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "head"));
+var_dump($dom->head?->nodeName);
+$tmp->remove();
+
+echo "--- prefixed head in right namespace ---\n";
+
+$tmp = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix:head"));
+var_dump($dom->head?->nodeName);
+$tmp->remove();
+
+echo "--- multiple head elements in right namespace ---\n";
+
+$tmp1 = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix1:head"));
+var_dump($dom->head?->nodeName);
+$tmp2 = $dom->documentElement->appendChild($dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix2:head"));
+var_dump($dom->head?->nodeName);
+$tmp1->remove();
+var_dump($dom->head?->nodeName);
+$tmp2->remove();
+var_dump($dom->head?->nodeName);
+
+echo "--- html element in no namespace ---\n";
+
+$dom = Dom\XMLDocument::createFromString(<<
+
+
+XML);
+var_dump($dom->head);
+
+?>
+--EXPECT--
+--- From parsing ---
+string(4) "HEAD"
+--- After head removal ---
+NULL
+--- head in no namespace ---
+NULL
+--- head in right namespace ---
+string(4) "HEAD"
+--- prefixed head in right namespace ---
+string(11) "PREFIX:HEAD"
+--- multiple head elements in right namespace ---
+string(12) "PREFIX1:HEAD"
+string(12) "PREFIX1:HEAD"
+string(12) "PREFIX2:HEAD"
+NULL
+--- html element in no namespace ---
+NULL
diff --git a/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_01.phpt b/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_01.phpt
index a82d882cd366c..593dae8e1f27e 100644
--- a/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_01.phpt
+++ b/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_01.phpt
@@ -23,7 +23,7 @@ var_dump(get_class($dom->getElementsByTagName("p")->item(0)));
?>
--EXPECT--
-object(Dom\HTMLDocument)#1 (26) {
+object(Dom\HTMLDocument)#1 (27) {
["implementation"]=>
string(22) "(object value omitted)"
["URL"]=>
@@ -48,6 +48,8 @@ object(Dom\HTMLDocument)#1 (26) {
int(1)
["body"]=>
string(22) "(object value omitted)"
+ ["head"]=>
+ string(22) "(object value omitted)"
["nodeType"]=>
int(13)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_02.phpt b/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_02.phpt
index 67565b13fbc32..b26205647786d 100644
--- a/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_02.phpt
+++ b/ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_02.phpt
@@ -23,7 +23,7 @@ var_dump(get_class($dom->getElementsByTagName("p")->item(0)));
?>
--EXPECT--
-object(Dom\HTMLDocument)#1 (26) {
+object(Dom\HTMLDocument)#1 (27) {
["implementation"]=>
string(22) "(object value omitted)"
["URL"]=>
@@ -48,6 +48,8 @@ object(Dom\HTMLDocument)#1 (26) {
int(1)
["body"]=>
string(22) "(object value omitted)"
+ ["head"]=>
+ string(22) "(object value omitted)"
["nodeType"]=>
int(13)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/spec/Document_implementation_createDocument.phpt b/ext/dom/tests/modern/spec/Document_implementation_createDocument.phpt
index 57313e4260247..37d73c5d40a7f 100644
--- a/ext/dom/tests/modern/spec/Document_implementation_createDocument.phpt
+++ b/ext/dom/tests/modern/spec/Document_implementation_createDocument.phpt
@@ -37,7 +37,7 @@ echo $dom->implementation->createDocument(null, "", $dtd)->saveXml(), "\n";
?>
--EXPECT--
--- (null, "") ---
-object(Dom\XMLDocument)#3 (30) {
+object(Dom\XMLDocument)#3 (31) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@@ -70,6 +70,8 @@ object(Dom\XMLDocument)#3 (30) {
int(0)
["body"]=>
NULL
+ ["head"]=>
+ NULL
["nodeType"]=>
int(9)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/xml/XMLDocument_debug.phpt b/ext/dom/tests/modern/xml/XMLDocument_debug.phpt
index 2d09e93e147eb..a0f6a528389b6 100644
--- a/ext/dom/tests/modern/xml/XMLDocument_debug.phpt
+++ b/ext/dom/tests/modern/xml/XMLDocument_debug.phpt
@@ -10,7 +10,7 @@ var_dump($dom);
?>
--EXPECT--
-object(Dom\XMLDocument)#1 (30) {
+object(Dom\XMLDocument)#1 (31) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@@ -43,6 +43,8 @@ object(Dom\XMLDocument)#1 (30) {
int(0)
["body"]=>
NULL
+ ["head"]=>
+ NULL
["nodeType"]=>
int(9)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/xml/XMLDocument_fromEmptyDocument_02.phpt b/ext/dom/tests/modern/xml/XMLDocument_fromEmptyDocument_02.phpt
index 8b8fd9d2003d6..b8a87e5ba4ea6 100644
--- a/ext/dom/tests/modern/xml/XMLDocument_fromEmptyDocument_02.phpt
+++ b/ext/dom/tests/modern/xml/XMLDocument_fromEmptyDocument_02.phpt
@@ -10,7 +10,7 @@ var_dump($dom);
?>
--EXPECT--
-object(Dom\XMLDocument)#1 (30) {
+object(Dom\XMLDocument)#1 (31) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@@ -43,6 +43,8 @@ object(Dom\XMLDocument)#1 (30) {
int(0)
["body"]=>
NULL
+ ["head"]=>
+ NULL
["nodeType"]=>
int(9)
["nodeName"]=>
diff --git a/ext/dom/tests/modern/xml/XMLDocument_node_ownerDocument_for_XML.phpt b/ext/dom/tests/modern/xml/XMLDocument_node_ownerDocument_for_XML.phpt
index ee2d5b0116b95..4f3308343e994 100644
--- a/ext/dom/tests/modern/xml/XMLDocument_node_ownerDocument_for_XML.phpt
+++ b/ext/dom/tests/modern/xml/XMLDocument_node_ownerDocument_for_XML.phpt
@@ -13,7 +13,7 @@ var_dump($element->ownerDocument);
?>
--EXPECTF--
-object(Dom\XMLDocument)#1 (30) {
+object(Dom\XMLDocument)#1 (31) {
["xmlEncoding"]=>
string(5) "UTF-8"
["xmlStandalone"]=>
@@ -46,6 +46,8 @@ object(Dom\XMLDocument)#1 (30) {
int(1)
["body"]=>
NULL
+ ["head"]=>
+ NULL
["nodeType"]=>
int(9)
["nodeName"]=>
From edbdaaa9a46f2a48c7219e52e449d1587828350a Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Sat, 23 Mar 2024 14:59:31 +0100
Subject: [PATCH 3/7] Implement Dom\Document::$body setter
---
ext/dom/dom_properties.h | 1 +
ext/dom/html_document.c | 73 ++++++++++++++++---
ext/dom/php_dom.c | 2 +-
...nt_body.phpt => Document_body_getter.phpt} | 2 +-
.../interactions/Document_body_setter.phpt | 48 ++++++++++++
.../Document_body_setter_errors.phpt | 61 ++++++++++++++++
6 files changed, 176 insertions(+), 11 deletions(-)
rename ext/dom/tests/modern/html/interactions/{Document_body.phpt => Document_body_getter.phpt} (98%)
create mode 100644 ext/dom/tests/modern/html/interactions/Document_body_setter.phpt
create mode 100644 ext/dom/tests/modern/html/interactions/Document_body_setter_errors.phpt
diff --git a/ext/dom/dom_properties.h b/ext/dom/dom_properties.h
index 5ab6fedfe59ea..1f8d1e44f34e2 100644
--- a/ext/dom/dom_properties.h
+++ b/ext/dom/dom_properties.h
@@ -63,6 +63,7 @@ zend_result dom_document_substitue_entities_write(dom_object *obj, zval *newval)
/* html5 document properties */
zend_result dom_html_document_encoding_write(dom_object *obj, zval *retval);
zend_result dom_html_document_body_read(dom_object *obj, zval *retval);
+zend_result dom_html_document_body_write(dom_object *obj, zval *newval);
zend_result dom_html_document_head_read(dom_object *obj, zval *retval);
/* documenttype properties */
diff --git a/ext/dom/html_document.c b/ext/dom/html_document.c
index 5cfb75fecdd79..e4fdf248a8905 100644
--- a/ext/dom/html_document.c
+++ b/ext/dom/html_document.c
@@ -1358,26 +1358,35 @@ zend_result dom_html_document_encoding_write(dom_object *obj, zval *newval)
return SUCCESS;
}
-zend_result dom_html_document_element_read_helper(dom_object *obj, zval *retval, bool (*accept)(const xmlChar *))
+static const xmlNode *dom_html_document_element_read_raw(const xmlDoc *docp, bool (*accept)(const xmlChar *))
{
- DOM_PROP_NODE(const xmlDoc *, docp, obj);
-
const xmlNode *root = xmlDocGetRootElement(docp);
if (root == NULL || !(php_dom_ns_is_fast(root, php_dom_ns_is_html_magic_token) && xmlStrEqual(root->name, BAD_CAST "html"))) {
- ZVAL_NULL(retval);
- return SUCCESS;
+ return NULL;
}
- xmlNodePtr cur = root->children;
+ const xmlNode *cur = root->children;
while (cur != NULL) {
if (cur->type == XML_ELEMENT_NODE && php_dom_ns_is_fast(cur, php_dom_ns_is_html_magic_token) && accept(cur->name)) {
- php_dom_create_object(cur, retval, obj);
- return SUCCESS;
+ return cur;
}
cur = cur->next;
}
- ZVAL_NULL(retval);
+ return NULL;
+}
+
+zend_result dom_html_document_element_read_helper(dom_object *obj, zval *retval, bool (*accept)(const xmlChar *))
+{
+ DOM_PROP_NODE(const xmlDoc *, docp, obj);
+
+ const xmlNode *element = dom_html_document_element_read_raw(docp, accept);
+ if (element == NULL) {
+ ZVAL_NULL(retval);
+ } else {
+ php_dom_create_object((xmlNodePtr) element, retval, obj);
+ }
+
return SUCCESS;
}
@@ -1403,4 +1412,50 @@ zend_result dom_html_document_head_read(dom_object *obj, zval *retval)
return dom_html_document_element_read_helper(obj, retval, dom_accept_head_name);
}
+/* https://html.spec.whatwg.org/#dom-document-body */
+zend_result dom_html_document_body_write(dom_object *obj, zval *newval)
+{
+ DOM_PROP_NODE(xmlDocPtr, docp, obj);
+
+ /* 1. If the new value is not a body or frameset element, then throw a "HierarchyRequestError" DOMException. */
+ if (Z_TYPE_P(newval) != IS_NULL) {
+ dom_object *newval_intern = Z_DOMOBJ_P(newval);
+ if (newval_intern->ptr != NULL) {
+ xmlNodePtr newval_node = ((php_libxml_node_ptr *) newval_intern->ptr)->node;
+ if (php_dom_ns_is_fast(newval_node, php_dom_ns_is_html_magic_token) && dom_accept_body_name(newval_node->name)) {
+ /* 2. If the new value is the same as the body element, return. */
+ const xmlNode *current_body_element = dom_html_document_element_read_raw(docp, dom_accept_body_name);
+ if (current_body_element == newval_node) {
+ return SUCCESS;
+ }
+
+ /* 3. If the body element is not null, then replace the body element with the new value within the body element's parent and return. */
+ if (current_body_element != NULL) {
+ php_dom_adopt_node(newval_node, obj, docp);
+ xmlNodePtr old = xmlReplaceNode((xmlNodePtr) current_body_element, newval_node);
+ if (old != NULL && old->_private == NULL) {
+ php_libxml_node_free_resource(old);
+ }
+ return SUCCESS;
+ }
+
+ /* 4. If there is no document element, throw a "HierarchyRequestError" DOMException. */
+ xmlNodePtr root = xmlDocGetRootElement(docp);
+ if (root == NULL) {
+ php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "A body can only be set if there is a document element", true);
+ return FAILURE;
+ }
+
+ /* 5. Append the new value to the document element. */
+ php_dom_adopt_node(newval_node, obj, docp);
+ xmlAddChild(root, newval_node);
+ return SUCCESS;
+ }
+ }
+ }
+
+ php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "The new body must either be a body or a frameset tag", true);
+ return FAILURE;
+}
+
#endif /* HAVE_LIBXML && HAVE_DOM */
diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c
index b08dc935a9a01..3c26afd77800d 100644
--- a/ext/dom/php_dom.c
+++ b/ext/dom/php_dom.c
@@ -851,7 +851,7 @@ PHP_MINIT_FUNCTION(dom)
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
- DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "body", dom_html_document_body_read, NULL);
+ DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "body", dom_html_document_body_read, dom_html_document_body_write);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "head", dom_html_document_head_read, NULL);
zend_hash_merge(&dom_abstract_base_document_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
/* No need to register in &classes because this is an abstract class handler. */
diff --git a/ext/dom/tests/modern/html/interactions/Document_body.phpt b/ext/dom/tests/modern/html/interactions/Document_body_getter.phpt
similarity index 98%
rename from ext/dom/tests/modern/html/interactions/Document_body.phpt
rename to ext/dom/tests/modern/html/interactions/Document_body_getter.phpt
index 5010e6e168bdc..79201ab3cf3b9 100644
--- a/ext/dom/tests/modern/html/interactions/Document_body.phpt
+++ b/ext/dom/tests/modern/html/interactions/Document_body_getter.phpt
@@ -1,5 +1,5 @@
--TEST--
-Test Dom\Document::$body
+Test Dom\Document::$body getter
--EXTENSIONS--
dom
--FILE--
diff --git a/ext/dom/tests/modern/html/interactions/Document_body_setter.phpt b/ext/dom/tests/modern/html/interactions/Document_body_setter.phpt
new file mode 100644
index 0000000000000..87cbb9b9c9e6e
--- /dev/null
+++ b/ext/dom/tests/modern/html/interactions/Document_body_setter.phpt
@@ -0,0 +1,48 @@
+--TEST--
+Test DOM\Document::$body setter
+--EXTENSIONS--
+dom
+--FILE--
+foo', LIBXML_NOERROR);
+$dom->body = $dom->body;
+var_dump($dom->body?->nodeName);
+
+echo "--- Add body when there is no body yet ---\n";
+$dom = DOM\HTMLDocument::createFromString('
foo
', LIBXML_NOERROR);
+$dom->body->remove();
+$dom->body = $dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix:body");
+var_dump($dom->body?->nodeName);
+
+echo "--- Replace old body with new body ---\n";
+$dom = DOM\HTMLDocument::createFromString('
foo
', LIBXML_NOERROR);
+$dom->body = $dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix:body");
+var_dump($dom->body?->nodeName);
+
+echo "--- Replace old body with new body, while still having a reference to the old body ---\n";
+$dom = DOM\HTMLDocument::createFromString('
foo
', LIBXML_NOERROR);
+$old = $dom->body;
+$dom->body = $dom->createElementNS("http://www.w3.org/1999/xhtml", "prefix:body");
+var_dump($dom->body?->nodeName);
+var_dump($old->nodeName);
+
+echo "--- Special note from the DOM spec ---\n";
+$dom = DOM\XMLDocument::createFromString('');
+$dom->body = $dom->createElementNS("http://www.w3.org/1999/xhtml", "body");
+var_dump($dom->body?->nodeName);
+
+?>
+--EXPECT--
+--- Replace body with itself ---
+string(4) "BODY"
+--- Add body when there is no body yet ---
+string(11) "PREFIX:BODY"
+--- Replace old body with new body ---
+string(11) "PREFIX:BODY"
+--- Replace old body with new body, while still having a reference to the old body ---
+string(11) "PREFIX:BODY"
+string(4) "BODY"
+--- Special note from the DOM spec ---
+NULL
diff --git a/ext/dom/tests/modern/html/interactions/Document_body_setter_errors.phpt b/ext/dom/tests/modern/html/interactions/Document_body_setter_errors.phpt
new file mode 100644
index 0000000000000..9913dbb12ae91
--- /dev/null
+++ b/ext/dom/tests/modern/html/interactions/Document_body_setter_errors.phpt
@@ -0,0 +1,61 @@
+--TEST--
+Test DOM\Document::$body setter errors
+--EXTENSIONS--
+dom
+--FILE--
+foo', LIBXML_NOERROR);
+ var_dump($dom->body?->nodeName);
+ try {
+ $dom->body = $cb($dom);
+ } catch (DOMException $e) {
+ echo $e->getMessage(), "\n";
+ }
+ var_dump($dom->body?->nodeName);
+}
+
+echo "--- Set body to NULL ---\n";
+testNormalReplace(fn ($dom) => NULL);
+
+echo "--- Wrong element tag in right namespace ---\n";
+testNormalReplace(fn ($dom) => $dom->createElementNS("http://www.w3.org/1999/xhtml", "foo"));
+
+echo "--- Right element tag in wrong namespace ---\n";
+testNormalReplace(fn ($dom) => $dom->createElementNS("urn:a", "body"));
+
+echo "--- Right element tag in no namespace ---\n";
+testNormalReplace(fn ($dom) => $dom->createElementNS("", "frameset"));
+
+echo "--- Set body without document element ---\n";
+$dom = DOM\XMLDocument::createEmpty();
+try {
+ $dom->body = $dom->createElementNS("http://www.w3.org/1999/xhtml", "body");
+} catch (DOMException $e) {
+ echo $e->getMessage(), "\n";
+}
+var_dump($dom->body?->nodeName);
+
+?>
+--EXPECT--
+--- Set body to NULL ---
+string(4) "BODY"
+The new body must either be a body or a frameset tag
+string(4) "BODY"
+--- Wrong element tag in right namespace ---
+string(4) "BODY"
+The new body must either be a body or a frameset tag
+string(4) "BODY"
+--- Right element tag in wrong namespace ---
+string(4) "BODY"
+The new body must either be a body or a frameset tag
+string(4) "BODY"
+--- Right element tag in no namespace ---
+string(4) "BODY"
+The new body must either be a body or a frameset tag
+string(4) "BODY"
+--- Set body without document element ---
+A body can only be set if there is a document element
+NULL
From 93b21c506bb016fc752255da3cd4d814daba318c Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Sun, 7 Apr 2024 14:24:01 +0200
Subject: [PATCH 4/7] Implement Dom\Document::$title getter
---
ext/dom/config.m4 | 2 +-
ext/dom/config.w32 | 2 +-
ext/dom/dom_properties.h | 1 +
ext/dom/html_document.c | 79 +++++++++++++++++++
ext/dom/infra.c | 77 ++++++++++++++++++
ext/dom/infra.h | 26 ++++++
ext/dom/php_dom.c | 1 +
ext/dom/php_dom.stub.php | 1 +
ext/dom/php_dom_arginfo.h | 8 +-
.../modern/common/Document_title_getter.phpt | 79 +++++++++++++++++++
...should_retain_properties_and_owner_01.phpt | 4 +-
...should_retain_properties_and_owner_02.phpt | 4 +-
...ocument_implementation_createDocument.phpt | 4 +-
.../tests/modern/xml/XMLDocument_debug.phpt | 4 +-
.../xml/XMLDocument_fromEmptyDocument_02.phpt | 4 +-
...MLDocument_node_ownerDocument_for_XML.phpt | 4 +-
16 files changed, 291 insertions(+), 9 deletions(-)
create mode 100644 ext/dom/infra.c
create mode 100644 ext/dom/infra.h
create mode 100644 ext/dom/tests/modern/common/Document_title_getter.phpt
diff --git a/ext/dom/config.m4 b/ext/dom/config.m4
index 4dcde6105a583..0db013b8689d1 100644
--- a/ext/dom/config.m4
+++ b/ext/dom/config.m4
@@ -25,7 +25,7 @@ if test "$PHP_DOM" != "no"; then
$LEXBOR_DIR/selectors/selectors.c \
$LEXBOR_DIR/ns/ns.c \
$LEXBOR_DIR/tag/tag.c"
- PHP_NEW_EXTENSION(dom, [php_dom.c attr.c document.c \
+ PHP_NEW_EXTENSION(dom, [php_dom.c attr.c document.c infra.c \
xml_document.c html_document.c xml_serializer.c html5_serializer.c html5_parser.c namespace_compat.c \
domexception.c parentnode.c \
processinginstruction.c cdatasection.c \
diff --git a/ext/dom/config.w32 b/ext/dom/config.w32
index 1a5d33bf7ca4f..02a7e0a9409d1 100644
--- a/ext/dom/config.w32
+++ b/ext/dom/config.w32
@@ -7,7 +7,7 @@ if (PHP_DOM == "yes") {
ADD_EXTENSION_DEP('dom', 'libxml') &&
CHECK_HEADER_ADD_INCLUDE("libxml/parser.h", "CFLAGS_DOM", PHP_PHP_BUILD + "\\include\\libxml2")
) {
- EXTENSION("dom", "php_dom.c attr.c document.c \
+ EXTENSION("dom", "php_dom.c attr.c document.c infra.c \
xml_document.c html_document.c xml_serializer.c html5_serializer.c html5_parser.c namespace_compat.c \
domexception.c parentnode.c processinginstruction.c \
cdatasection.c documentfragment.c domimplementation.c element.c \
diff --git a/ext/dom/dom_properties.h b/ext/dom/dom_properties.h
index 1f8d1e44f34e2..039f7fd260e1c 100644
--- a/ext/dom/dom_properties.h
+++ b/ext/dom/dom_properties.h
@@ -65,6 +65,7 @@ zend_result dom_html_document_encoding_write(dom_object *obj, zval *retval);
zend_result dom_html_document_body_read(dom_object *obj, zval *retval);
zend_result dom_html_document_body_write(dom_object *obj, zval *newval);
zend_result dom_html_document_head_read(dom_object *obj, zval *retval);
+zend_result dom_html_document_title_read(dom_object *obj, zval *retval);
/* documenttype properties */
zend_result dom_documenttype_name_read(dom_object *obj, zval *retval);
diff --git a/ext/dom/html_document.c b/ext/dom/html_document.c
index e4fdf248a8905..d487d04b7fbbe 100644
--- a/ext/dom/html_document.c
+++ b/ext/dom/html_document.c
@@ -21,6 +21,7 @@
#include "php.h"
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
#include "php_dom.h"
+#include "infra.h"
#include "html5_parser.h"
#include "html5_serializer.h"
#include "namespace_compat.h"
@@ -1458,4 +1459,82 @@ zend_result dom_html_document_body_write(dom_object *obj, zval *newval)
return FAILURE;
}
+/* https://dom.spec.whatwg.org/#concept-child-text-content */
+static zend_string *dom_get_child_text_content(const xmlNode *node)
+{
+ smart_str content = {0};
+
+ const xmlNode *text = node->children;
+ while (text != NULL) {
+ if (text->type == XML_TEXT_NODE || text->type == XML_CDATA_SECTION_NODE) {
+ smart_str_appends(&content, (const char *) text->content);
+ }
+ text = text->next;
+ }
+
+ return smart_str_extract(&content);
+}
+
+/* https://html.spec.whatwg.org/#the-title-element-2 */
+static const xmlNode *dom_get_title_element(const xmlDoc *doc)
+{
+ const xmlNode *node = doc->children;
+
+ while (node != NULL) {
+ if (node->type == XML_ELEMENT_NODE) {
+ if (php_dom_ns_is_fast(node, php_dom_ns_is_html_magic_token) && xmlStrEqual(node->name, BAD_CAST "title")) {
+ break;
+ }
+ }
+
+ node = php_dom_next_in_tree_order(node, NULL);
+ }
+
+ return node;
+}
+
+/* https://html.spec.whatwg.org/#document.title */
+zend_result dom_html_document_title_read(dom_object *obj, zval *retval)
+{
+ DOM_PROP_NODE(const xmlDoc *, docp, obj);
+ const xmlNode *root = xmlDocGetRootElement(docp);
+
+ if (root == NULL) {
+ ZVAL_EMPTY_STRING(retval);
+ return SUCCESS;
+ }
+
+ zend_string *value = zend_empty_string;
+
+ /* 1. If the document element is an SVG svg element,
+ * then let value be the child text content of the first SVG title element that is a child of the document element. */
+ if (php_dom_ns_is_fast(root, php_dom_ns_is_svg_magic_token) && xmlStrEqual(root->name, BAD_CAST "svg")) {
+ const xmlNode *cur = root->children;
+
+ while (cur != NULL) {
+ if (cur->type == XML_ELEMENT_NODE
+ && php_dom_ns_is_fast(cur, php_dom_ns_is_svg_magic_token) && xmlStrEqual(cur->name, BAD_CAST "title")) {
+ value = dom_get_child_text_content(cur);
+ break;
+ }
+ cur = cur->next;
+ }
+ } else {
+ /* 2. Otherwise, let value be the child text content of the title element,
+ * or the empty string if the title element is null. */
+ const xmlNode *title = dom_get_title_element(docp);
+ if (title != NULL) {
+ value = dom_get_child_text_content(title);
+ }
+ }
+
+ /* 3. Strip and collapse ASCII whitespace in value. */
+ value = dom_strip_and_collapse_ascii_whitespace(value);
+
+ /* 4. Return value. */
+ ZVAL_STR(retval, value);
+
+ return SUCCESS;
+}
+
#endif /* HAVE_LIBXML && HAVE_DOM */
diff --git a/ext/dom/infra.c b/ext/dom/infra.c
new file mode 100644
index 0000000000000..8fa42453c7519
--- /dev/null
+++ b/ext/dom/infra.c
@@ -0,0 +1,77 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright (c) The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | https://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Niels Dossche |
+ +----------------------------------------------------------------------+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
+#include "infra.h"
+
+/* https://infra.spec.whatwg.org/#ascii-whitespace */
+const char *ascii_whitespace = "\x09\x0A\x0C\x0D\x20";
+
+/* https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace */
+zend_string *dom_strip_and_collapse_ascii_whitespace(zend_string *input)
+{
+ if (input == zend_empty_string) {
+ return input;
+ }
+
+ ZEND_ASSERT(!ZSTR_IS_INTERNED(input));
+ ZEND_ASSERT(GC_REFCOUNT(input) == 1);
+
+ char *write_ptr = ZSTR_VAL(input);
+
+ const char *start = ZSTR_VAL(input);
+ const char *current = start;
+ const char *end = current + ZSTR_LEN(input);
+
+ current += strspn(current, ascii_whitespace);
+
+ while (current < end) {
+ /* Copy non-whitespace */
+ size_t non_whitespace_len = strcspn(current, ascii_whitespace);
+ /* If the pointers are equal, we still haven't encountered collapsable or strippable whitespace. */
+ if (write_ptr != current) {
+ memmove(write_ptr, current, non_whitespace_len);
+ }
+ current += non_whitespace_len;
+ write_ptr += non_whitespace_len;
+
+ /* Skip whitespace */
+ current += strspn(current, ascii_whitespace);
+ if (current < end) {
+ /* Only make a space when we're not yet at the end of the input, because that means more non-whitespace
+ * input is to come. */
+ *write_ptr++ = ' ';
+ }
+ }
+
+ *write_ptr = '\0';
+
+ size_t len = write_ptr - start;
+ if (len != ZSTR_LEN(input)) {
+ return zend_string_truncate(input, len, false);
+ } else {
+ /* Forget the hash value since we may have transformed non-space-whitespace into spaces. */
+ zend_string_forget_hash_val(input);
+ return input;
+ }
+}
+
+#endif /* HAVE_LIBXML && HAVE_DOM */
diff --git a/ext/dom/infra.h b/ext/dom/infra.h
new file mode 100644
index 0000000000000..d84ad5a2a0efa
--- /dev/null
+++ b/ext/dom/infra.h
@@ -0,0 +1,26 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright (c) The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | https://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Niels Dossche |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef INFRA_H
+#define INFRA_H
+
+#include "zend_string.h"
+
+extern const char *ascii_whitespace;
+
+zend_string *dom_strip_and_collapse_ascii_whitespace(zend_string *input);
+
+#endif
diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c
index 3c26afd77800d..44fa0a5489d9d 100644
--- a/ext/dom/php_dom.c
+++ b/ext/dom/php_dom.c
@@ -853,6 +853,7 @@ PHP_MINIT_FUNCTION(dom)
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "body", dom_html_document_body_read, dom_html_document_body_write);
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "head", dom_html_document_head_read, NULL);
+ DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "title", dom_html_document_title_read, NULL);
zend_hash_merge(&dom_abstract_base_document_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
/* No need to register in &classes because this is an abstract class handler. */
diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php
index a21b818b89994..e2d374b7efa4d 100644
--- a/ext/dom/php_dom.stub.php
+++ b/ext/dom/php_dom.stub.php
@@ -1584,6 +1584,7 @@ public function importLegacyNode(\DOMNode $node, bool $deep = false): Node {}
public ?Element $body;
/** @readonly */
public ?Element $head;
+ public string $title;
}
final class HTMLDocument extends Document
diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h
index b364952687e7f..c3cc618757571 100644
--- a/ext/dom/php_dom_arginfo.h
+++ b/ext/dom/php_dom_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 0795d4e52f62ab33df92cfbdd5178223fbfc3eeb */
+ * Stub hash: 7a2c28838f431eff28dea8cc5356dbcd38921592 */
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)
@@ -3456,6 +3456,12 @@ static zend_class_entry *register_class_Dom_Document(zend_class_entry *class_ent
zend_declare_typed_property(class_entry, property_head_name, &property_head_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_head_class_Dom_Element, 0, MAY_BE_NULL));
zend_string_release(property_head_name);
+ zval property_title_default_value;
+ ZVAL_UNDEF(&property_title_default_value);
+ zend_string *property_title_name = zend_string_init("title", sizeof("title") - 1, 1);
+ zend_declare_typed_property(class_entry, property_title_name, &property_title_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
+ zend_string_release(property_title_name);
+
return class_entry;
}
diff --git a/ext/dom/tests/modern/common/Document_title_getter.phpt b/ext/dom/tests/modern/common/Document_title_getter.phpt
new file mode 100644
index 0000000000000..eb4f4b7cdb5fd
--- /dev/null
+++ b/ext/dom/tests/modern/common/Document_title_getter.phpt
@@ -0,0 +1,79 @@
+--TEST--
+Dom\Document::$title getter
+--EXTENSIONS--
+dom
+--FILE--
+A normal title without collapsable or strippable whitespace");
+var_dump($dom->title);
+
+$dom = Dom\XMLDocument::createFromString(" only ws at front");
+var_dump($dom->title);
+
+$dom = Dom\XMLDocument::createFromString("only ws at back ");
+var_dump($dom->title);
+
+$dom = Dom\XMLDocument::createFromString("