Skip to content

Commit eb0f710

Browse files
committed
Implement DOM\Document::$title getter
1 parent c7307f7 commit eb0f710

16 files changed

+271
-9
lines changed

ext/dom/config.m4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ if test "$PHP_DOM" != "no"; then
2525
$LEXBOR_DIR/selectors/selectors.c \
2626
$LEXBOR_DIR/ns/ns.c \
2727
$LEXBOR_DIR/tag/tag.c"
28-
PHP_NEW_EXTENSION(dom, [php_dom.c attr.c document.c \
28+
PHP_NEW_EXTENSION(dom, [php_dom.c attr.c document.c infra.c \
2929
xml_document.c html_document.c xml_serializer.c html5_serializer.c html5_parser.c namespace_compat.c \
3030
domexception.c parentnode.c \
3131
processinginstruction.c cdatasection.c \

ext/dom/config.w32

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ if (PHP_DOM == "yes") {
77
ADD_EXTENSION_DEP('dom', 'libxml') &&
88
CHECK_HEADER_ADD_INCLUDE("libxml/parser.h", "CFLAGS_DOM", PHP_PHP_BUILD + "\\include\\libxml2")
99
) {
10-
EXTENSION("dom", "php_dom.c attr.c document.c \
10+
EXTENSION("dom", "php_dom.c attr.c document.c infra.c \
1111
xml_document.c html_document.c xml_serializer.c html5_serializer.c html5_parser.c namespace_compat.c \
1212
domexception.c parentnode.c processinginstruction.c \
1313
cdatasection.c documentfragment.c domimplementation.c element.c \

ext/dom/dom_properties.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ zend_result dom_html_document_encoding_write(dom_object *obj, zval *retval);
6767
zend_result dom_html_document_body_read(dom_object *obj, zval *retval);
6868
zend_result dom_html_document_body_write(dom_object *obj, zval *newval);
6969
zend_result dom_html_document_head_read(dom_object *obj, zval *retval);
70+
zend_result dom_html_document_title_read(dom_object *obj, zval *retval);
7071

7172
/* documenttype properties */
7273
zend_result dom_documenttype_name_read(dom_object *obj, zval *retval);

ext/dom/html_document.c

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,4 +1459,85 @@ zend_result dom_html_document_body_write(dom_object *obj, zval *newval)
14591459
return FAILURE;
14601460
}
14611461

1462+
/* https://dom.spec.whatwg.org/#concept-child-text-content */
1463+
static zend_string *dom_get_child_text_content(const xmlNode *node)
1464+
{
1465+
smart_str content = {0};
1466+
1467+
const xmlNode *text = node->children;
1468+
while (text != NULL) {
1469+
if (text->type == XML_TEXT_NODE || text->type == XML_CDATA_SECTION_NODE) {
1470+
smart_str_appends(&content, (const char *) text->content);
1471+
}
1472+
text = text->next;
1473+
}
1474+
1475+
return smart_str_extract(&content);
1476+
}
1477+
1478+
/* https://html.spec.whatwg.org/#the-title-element-2 */
1479+
static const xmlNode *dom_get_title_element(const xmlDoc *doc)
1480+
{
1481+
const xmlNode *node = doc->children;
1482+
1483+
while (node != NULL) {
1484+
if (node->type == XML_ELEMENT_NODE) {
1485+
if (php_dom_ns_is_fast(node, php_dom_ns_is_html_magic_token) && xmlStrEqual(node->name, BAD_CAST "title")) {
1486+
break;
1487+
} else if (node->children) {
1488+
node = node->children;
1489+
continue;
1490+
}
1491+
}
1492+
1493+
node = php_dom_next_in_tree_order(node, NULL);
1494+
}
1495+
1496+
return node;
1497+
}
1498+
1499+
/* https://html.spec.whatwg.org/#document.title */
1500+
zend_result dom_html_document_title_read(dom_object *obj, zval *retval)
1501+
{
1502+
DOM_PROP_NODE(const xmlDoc *, docp, obj);
1503+
const xmlNode *root = xmlDocGetRootElement(docp);
1504+
1505+
if (root == NULL) {
1506+
ZVAL_EMPTY_STRING(retval);
1507+
return SUCCESS;
1508+
}
1509+
1510+
zend_string *value = zend_empty_string;
1511+
1512+
/* 1. If the document element is an SVG svg element,
1513+
* then let value be the child text content of the first SVG title element that is a child of the document element. */
1514+
if (php_dom_ns_is_fast(root, php_dom_ns_is_svg_magic_token) && xmlStrEqual(root->name, BAD_CAST "svg")) {
1515+
const xmlNode *cur = root->children;
1516+
1517+
while (cur != NULL) {
1518+
if (cur->type == XML_ELEMENT_NODE
1519+
&& php_dom_ns_is_fast(cur, php_dom_ns_is_svg_magic_token) && xmlStrEqual(cur->name, BAD_CAST "title")) {
1520+
value = dom_get_child_text_content(cur);
1521+
break;
1522+
}
1523+
cur = cur->next;
1524+
}
1525+
} else {
1526+
/* 2. Otherwise, let value be the child text content of the title element,
1527+
* or the empty string if the title element is null. */
1528+
const xmlNode *title = dom_get_title_element(docp);
1529+
if (title != NULL) {
1530+
value = dom_get_child_text_content(title);
1531+
}
1532+
}
1533+
1534+
/* 3. Strip and collapse ASCII whitespace in value. */
1535+
value = dom_strip_and_collapse_ascii_whitespace(value);
1536+
1537+
/* 4. Return value. */
1538+
ZVAL_STR(retval, value);
1539+
1540+
return SUCCESS;
1541+
}
1542+
14621543
#endif /* HAVE_LIBXML && HAVE_DOM */

ext/dom/infra.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Copyright (c) The PHP Group |
4+
+----------------------------------------------------------------------+
5+
| This source file is subject to version 3.01 of the PHP license, |
6+
| that is bundled with this package in the file LICENSE, and is |
7+
| available through the world-wide-web at the following url: |
8+
| https://www.php.net/license/3_01.txt |
9+
| If you did not receive a copy of the PHP license and are unable to |
10+
| obtain it through the world-wide-web, please send a note to |
11+
| license@php.net so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Authors: Niels Dossche <nielsdos@php.net> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#ifdef HAVE_CONFIG_H
18+
#include "config.h"
19+
#endif
20+
21+
#include "php.h"
22+
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
23+
#include "php_dom.h"
24+
25+
/* https://infra.spec.whatwg.org/#ascii-whitespace */
26+
static const char *ascii_whitespace = "\x09\x0A\x0C\x0D\x20";
27+
28+
/* https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace */
29+
zend_string *dom_strip_and_collapse_ascii_whitespace(zend_string *input)
30+
{
31+
if (input == zend_empty_string) {
32+
return input;
33+
}
34+
35+
ZEND_ASSERT(!ZSTR_IS_INTERNED(input));
36+
ZEND_ASSERT(GC_REFCOUNT(input) == 1);
37+
38+
char *write_ptr = ZSTR_VAL(input);
39+
40+
const char *start = ZSTR_VAL(input);
41+
const char *current = start;
42+
const char *end = current + ZSTR_LEN(input);
43+
44+
current += strspn(current, ascii_whitespace);
45+
46+
while (current < end) {
47+
/* Copy non-whitespace */
48+
size_t non_whitespace_len = strcspn(current, ascii_whitespace);
49+
/* If the pointers are equal, we still haven't encountered collapsable or strippable whitespace. */
50+
if (write_ptr != current) {
51+
memmove(write_ptr, current, non_whitespace_len);
52+
}
53+
current += non_whitespace_len;
54+
write_ptr += non_whitespace_len;
55+
56+
/* Skip whitespace */
57+
current += strspn(current, ascii_whitespace);
58+
if (current < end) {
59+
/* Only make a space when we're not yet at the end of the input, because that means more non-whitespace
60+
* input is to come. */
61+
*write_ptr++ = ' ';
62+
}
63+
}
64+
65+
*write_ptr = '\0';
66+
67+
size_t len = write_ptr - start;
68+
if (len != ZSTR_LEN(input)) {
69+
ZSTR_LEN(input) = len;
70+
return zend_string_truncate(input, len, false);
71+
} else {
72+
/* Forget the hash value since we may have transformed non-space-whitespace into spaces. */
73+
zend_string_forget_hash_val(input);
74+
return input;
75+
}
76+
}
77+
78+
#endif /* HAVE_LIBXML && HAVE_DOM */

ext/dom/php_dom.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,7 @@ PHP_MINIT_FUNCTION(dom)
841841
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
842842
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "body", dom_html_document_body_read, dom_html_document_body_write);
843843
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "head", dom_html_document_head_read, NULL);
844+
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "title", dom_html_document_title_read, NULL);
844845
zend_hash_merge(&dom_abstract_base_document_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
845846
/* No need to register in &classes because this is an abstract class handler. */
846847

ext/dom/php_dom.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ int php_dom_get_nodelist_length(dom_object *obj);
209209

210210
xmlNodePtr dom_clone_node(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr node, xmlDocPtr doc, bool recursive);
211211

212+
/* infra */
213+
zend_string *dom_strip_and_collapse_ascii_whitespace(zend_string *input);
214+
212215
#define DOM_GET_INTERN(__id, __intern) { \
213216
__intern = Z_DOMOBJ_P(__id); \
214217
if (UNEXPECTED(__intern->ptr == NULL)) { \

ext/dom/php_dom.stub.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1588,6 +1588,7 @@ public function importLegacyNode(\DOMNode $node, bool $deep = false): Node {}
15881588
public ?Element $body;
15891589
/** @readonly */
15901590
public ?Element $head;
1591+
public string $title;
15911592
}
15921593

15931594
final class HTMLDocument extends Document

ext/dom/php_dom_arginfo.h

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
--TEST--
2+
DOM\Document::$title getter
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
echo "=== HTML namespaced root ===\n";
9+
10+
$dom = DOM\XMLDocument::createFromString("<root xmlns=\"http://www.w3.org/1999/xhtml\"><title>A normal title without collapsable or strippable whitespace</title></root>");
11+
var_dump($dom->title);
12+
13+
$dom = DOM\XMLDocument::createFromString("<root xmlns=\"http://www.w3.org/1999/xhtml\"><title> only ws at front</title></root>");
14+
var_dump($dom->title);
15+
16+
$dom = DOM\XMLDocument::createFromString("<root xmlns=\"http://www.w3.org/1999/xhtml\"><title>only ws at back </title></root>");
17+
var_dump($dom->title);
18+
19+
$dom = DOM\XMLDocument::createFromString("<root xmlns=\"http://www.w3.org/1999/xhtml\"><ignoreme/><div><title>first</title></div><title>second</title></root>");
20+
var_dump($dom->title);
21+
22+
$dom = DOM\XMLDocument::createFromString("<title xmlns=\"http://www.w3.org/1999/xhtml\">title</title>");
23+
var_dump($dom->title);
24+
25+
$dom = DOM\XMLDocument::createFromString("<root xmlns=\"http://www.w3.org/1999/xhtml\"><title> abc def ghi </title></root>");
26+
var_dump($dom->title);
27+
28+
$dom = DOM\XMLDocument::createFromString("<root xmlns=\"http://www.w3.org/1999/xhtml\"><title></title></root>");
29+
var_dump($dom->title);
30+
31+
$dom = DOM\XMLDocument::createFromString("<root xmlns=\"http://www.w3.org/1999/xhtml\"></root>");
32+
var_dump($dom->title);
33+
34+
$dom = DOM\XMLDocument::createFromString("<root xmlns=\"http://www.w3.org/1999/xhtml\"><title> \t\r\n </title></root>");
35+
var_dump($dom->title);
36+
37+
$dom = DOM\XMLDocument::createFromString("<root xmlns=\"http://www.w3.org/1999/xhtml\"><title> \tx<?y y?><![CDATA[z]]>\n </title></root>");
38+
var_dump($dom->title);
39+
40+
$dom = DOM\XMLDocument::createFromString("<root xmlns=\"http://www.w3.org/1999/xhtml\"><title><div><!-- comment -->x</div>y<p>z</p>w</title></root>");
41+
var_dump($dom->title);
42+
43+
$dom = DOM\XMLDocument::createFromString("<root xmlns=\"http://www.w3.org/1999/xhtml\"><title>title\nhere</title></root>");
44+
var_dump($dom->title);
45+
46+
echo "=== SVG namespaced root ===\n";
47+
48+
$dom = DOM\XMLDocument::createFromString("<root xmlns=\"http://www.w3.org/1999/xhtml\"><title>title</title></root>");
49+
var_dump($dom->title);
50+
51+
$dom = DOM\XMLDocument::createFromString("<svg xmlns=\"http://www.w3.org/1999/xhtml\"><title xmlns=\"http://www.w3.org/1999/xhtml\">title</title></svg>");
52+
var_dump($dom->title);
53+
54+
$dom = DOM\XMLDocument::createFromString("<svg xmlns=\"http://www.w3.org/1999/xhtml\"><title xmlns=\"http://www.w3.org/1999/xhtml\">title</title><foo/><title>hi</title></svg>");
55+
var_dump($dom->title);
56+
57+
$dom = DOM\XMLDocument::createFromString("<svg xmlns=\"http://www.w3.org/1999/xhtml\"/>");
58+
var_dump($dom->title);
59+
60+
?>
61+
--EXPECT--
62+
=== HTML namespaced root ===
63+
string(59) "A normal title without collapsable or strippable whitespace"
64+
string(16) "only ws at front"
65+
string(15) "only ws at back"
66+
string(5) "first"
67+
string(5) "title"
68+
string(11) "abc def ghi"
69+
string(0) ""
70+
string(0) ""
71+
string(0) ""
72+
string(2) "xz"
73+
string(2) "yw"
74+
string(10) "title here"
75+
=== SVG namespaced root ===
76+
string(5) "title"
77+
string(5) "title"
78+
string(5) "title"
79+
string(0) ""

ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_01.phpt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ var_dump(get_class($dom->getElementsByTagName("p")->item(0)));
2323

2424
?>
2525
--EXPECT--
26-
object(DOM\HTMLDocument)#1 (27) {
26+
object(DOM\HTMLDocument)#1 (28) {
2727
["implementation"]=>
2828
string(22) "(object value omitted)"
2929
["URL"]=>
@@ -50,6 +50,8 @@ object(DOM\HTMLDocument)#1 (27) {
5050
string(22) "(object value omitted)"
5151
["head"]=>
5252
string(22) "(object value omitted)"
53+
["title"]=>
54+
string(0) ""
5355
["nodeType"]=>
5456
int(13)
5557
["nodeName"]=>

ext/dom/tests/modern/html/interactions/HTMLDocument_should_retain_properties_and_owner_02.phpt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ var_dump(get_class($dom->getElementsByTagName("p")->item(0)));
2323

2424
?>
2525
--EXPECT--
26-
object(DOM\HTMLDocument)#1 (27) {
26+
object(DOM\HTMLDocument)#1 (28) {
2727
["implementation"]=>
2828
string(22) "(object value omitted)"
2929
["URL"]=>
@@ -50,6 +50,8 @@ object(DOM\HTMLDocument)#1 (27) {
5050
string(22) "(object value omitted)"
5151
["head"]=>
5252
string(22) "(object value omitted)"
53+
["title"]=>
54+
string(0) ""
5355
["nodeType"]=>
5456
int(13)
5557
["nodeName"]=>

ext/dom/tests/modern/spec/Document_implementation_createDocument.phpt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ echo $dom->implementation->createDocument(null, "", $dtd)->saveXML(), "\n";
3737
?>
3838
--EXPECT--
3939
--- (null, "") ---
40-
object(DOM\XMLDocument)#3 (31) {
40+
object(DOM\XMLDocument)#3 (32) {
4141
["xmlEncoding"]=>
4242
string(5) "UTF-8"
4343
["xmlStandalone"]=>
@@ -72,6 +72,8 @@ object(DOM\XMLDocument)#3 (31) {
7272
NULL
7373
["head"]=>
7474
NULL
75+
["title"]=>
76+
string(0) ""
7577
["nodeType"]=>
7678
int(9)
7779
["nodeName"]=>

ext/dom/tests/modern/xml/XMLDocument_debug.phpt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ var_dump($dom);
1010

1111
?>
1212
--EXPECT--
13-
object(DOM\XMLDocument)#1 (31) {
13+
object(DOM\XMLDocument)#1 (32) {
1414
["xmlEncoding"]=>
1515
string(5) "UTF-8"
1616
["xmlStandalone"]=>
@@ -45,6 +45,8 @@ object(DOM\XMLDocument)#1 (31) {
4545
NULL
4646
["head"]=>
4747
NULL
48+
["title"]=>
49+
string(0) ""
4850
["nodeType"]=>
4951
int(9)
5052
["nodeName"]=>

0 commit comments

Comments
 (0)