Skip to content

Commit dd2f1a4

Browse files
committed
Implement DOM\Document::$title setter
1 parent 83ecb87 commit dd2f1a4

File tree

4 files changed

+223
-15
lines changed

4 files changed

+223
-15
lines changed

ext/dom/dom_properties.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ 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);
7070
zend_result dom_html_document_title_read(dom_object *obj, zval *retval);
71+
zend_result dom_html_document_title_write(dom_object *obj, zval *newval);
7172

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

ext/dom/html_document.c

Lines changed: 116 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,14 +1359,14 @@ zend_result dom_html_document_encoding_write(dom_object *obj, zval *newval)
13591359
return SUCCESS;
13601360
}
13611361

1362-
static const xmlNode *dom_html_document_element_read_raw(const xmlDoc *docp, bool (*accept)(const xmlChar *))
1362+
static xmlNodePtr dom_html_document_element_read_raw(const xmlDoc *docp, bool (*accept)(const xmlChar *))
13631363
{
13641364
const xmlNode *root = xmlDocGetRootElement(docp);
13651365
if (root == NULL || !(php_dom_ns_is_fast(root, php_dom_ns_is_html_magic_token) && xmlStrEqual(root->name, BAD_CAST "html"))) {
13661366
return NULL;
13671367
}
13681368

1369-
const xmlNode *cur = root->children;
1369+
xmlNodePtr cur = root->children;
13701370
while (cur != NULL) {
13711371
if (cur->type == XML_ELEMENT_NODE && php_dom_ns_is_fast(cur, php_dom_ns_is_html_magic_token) && accept(cur->name)) {
13721372
return cur;
@@ -1476,9 +1476,9 @@ static zend_string *dom_get_child_text_content(const xmlNode *node)
14761476
}
14771477

14781478
/* https://html.spec.whatwg.org/#the-title-element-2 */
1479-
static const xmlNode *dom_get_title_element(const xmlDoc *doc)
1479+
static xmlNodePtr dom_get_title_element(const xmlDoc *doc)
14801480
{
1481-
const xmlNode *node = doc->children;
1481+
xmlNodePtr node = doc->children;
14821482

14831483
while (node != NULL) {
14841484
if (node->type == XML_ELEMENT_NODE) {
@@ -1496,11 +1496,28 @@ static const xmlNode *dom_get_title_element(const xmlDoc *doc)
14961496
return node;
14971497
}
14981498

1499+
/* The subtle difference is that this is about the direct title descendant of the svg element,
1500+
* whereas the html variant of this function is about the first in-tree title element. */
1501+
static xmlNodePtr dom_get_svg_title_element(xmlNodePtr svg)
1502+
{
1503+
xmlNodePtr cur = svg->children;
1504+
1505+
while (cur != NULL) {
1506+
if (cur->type == XML_ELEMENT_NODE
1507+
&& php_dom_ns_is_fast(cur, php_dom_ns_is_svg_magic_token) && xmlStrEqual(cur->name, BAD_CAST "title")) {
1508+
break;
1509+
}
1510+
cur = cur->next;
1511+
}
1512+
1513+
return cur;
1514+
}
1515+
14991516
/* https://html.spec.whatwg.org/#document.title */
15001517
zend_result dom_html_document_title_read(dom_object *obj, zval *retval)
15011518
{
15021519
DOM_PROP_NODE(const xmlDoc *, docp, obj);
1503-
const xmlNode *root = xmlDocGetRootElement(docp);
1520+
xmlNodePtr root = xmlDocGetRootElement(docp);
15041521

15051522
if (root == NULL) {
15061523
ZVAL_EMPTY_STRING(retval);
@@ -1512,15 +1529,9 @@ zend_result dom_html_document_title_read(dom_object *obj, zval *retval)
15121529
/* 1. If the document element is an SVG svg element,
15131530
* then let value be the child text content of the first SVG title element that is a child of the document element. */
15141531
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;
1532+
const xmlNode *title = dom_get_svg_title_element(root);
1533+
if (title != NULL) {
1534+
value = dom_get_child_text_content(title);
15241535
}
15251536
} else {
15261537
/* 2. Otherwise, let value be the child text content of the title element,
@@ -1540,4 +1551,95 @@ zend_result dom_html_document_title_read(dom_object *obj, zval *retval)
15401551
return SUCCESS;
15411552
}
15421553

1554+
static void dom_string_replace_all(xmlDocPtr docp, xmlNodePtr element, zval *zv)
1555+
{
1556+
dom_remove_all_children(element);
1557+
xmlNode *text = xmlNewDocText(docp, BAD_CAST Z_STRVAL_P(zv));
1558+
xmlAddChild(element, text);
1559+
}
1560+
1561+
/* https://html.spec.whatwg.org/#document.title */
1562+
zend_result dom_html_document_title_write(dom_object *obj, zval *newval)
1563+
{
1564+
DOM_PROP_NODE(xmlDocPtr, docp, obj);
1565+
xmlNodePtr root = xmlDocGetRootElement(docp);
1566+
1567+
if (root == NULL) {
1568+
return SUCCESS;
1569+
}
1570+
1571+
/* If the document element is an SVG svg element */
1572+
if (php_dom_ns_is_fast(root, php_dom_ns_is_svg_magic_token) && xmlStrEqual(root->name, BAD_CAST "svg")) {
1573+
/* 1. If there is an SVG title element that is a child of the document element, let element be the first such element. */
1574+
xmlNodePtr element = dom_get_svg_title_element(root);
1575+
1576+
/* 2. Otherwise: */
1577+
if (element == NULL) {
1578+
/* 2.1. Let element be the result of creating an element given the document element's node document,
1579+
* title, and the SVG namespace. */
1580+
1581+
/* Annoyingly, we must create it in the svg namespace _without_ prefix... */
1582+
xmlNsPtr ns = root->ns;
1583+
if (ns->prefix != NULL) {
1584+
/* Slow path... */
1585+
php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(obj);
1586+
zend_string *href = ZSTR_INIT_LITERAL(DOM_SVG_NS_URI, false);
1587+
ns = php_dom_libxml_ns_mapper_get_ns(ns_mapper, zend_empty_string, href);
1588+
zend_string_release_ex(href, false);
1589+
}
1590+
1591+
element = xmlNewDocNode(docp, ns, BAD_CAST "title", NULL);
1592+
if (UNEXPECTED(element == NULL)) {
1593+
php_dom_throw_error(INVALID_STATE_ERR, true);
1594+
return FAILURE;
1595+
}
1596+
1597+
/* 2.2. Insert element as the first child of the document element. */
1598+
if (root->children == NULL) {
1599+
root->last = element;
1600+
} else {
1601+
element->next = root->children;
1602+
root->children->prev = element;
1603+
}
1604+
root->children = element;
1605+
element->parent = root;
1606+
}
1607+
1608+
/* 3. String replace all with the given value within element. */
1609+
dom_string_replace_all(docp, element, newval);
1610+
}
1611+
/* If the document element is in the HTML namespace */
1612+
else if (php_dom_ns_is_fast(root, php_dom_ns_is_html_magic_token)) {
1613+
/* 1. If the title element is null and the head element is null, then return. */
1614+
xmlNodePtr title = dom_get_title_element(docp);
1615+
xmlNodePtr head = dom_html_document_element_read_raw(docp, dom_accept_head_name);
1616+
if (title == NULL && head == NULL) {
1617+
return SUCCESS;
1618+
}
1619+
1620+
/* 2. If the title element is non-null, let element be the title element. */
1621+
xmlNodePtr element = title;
1622+
1623+
/* 3. Otherwise: */
1624+
if (element == NULL) {
1625+
/* 3.1. Let element be the result of creating an element given the document element's node document, title,
1626+
* and the HTML namespace. */
1627+
php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(obj);
1628+
element = xmlNewDocNode(docp, php_dom_libxml_ns_mapper_ensure_html_ns(ns_mapper), BAD_CAST "title", NULL);
1629+
if (UNEXPECTED(element == NULL)) {
1630+
php_dom_throw_error(INVALID_STATE_ERR, true);
1631+
return FAILURE;
1632+
}
1633+
1634+
/* 3.2. Append element to the head element. */
1635+
xmlAddChild(head, element);
1636+
}
1637+
1638+
/* 4. String replace all with the given value within element. */
1639+
dom_string_replace_all(docp, element, newval);
1640+
}
1641+
1642+
return SUCCESS;
1643+
}
1644+
15431645
#endif /* HAVE_LIBXML && HAVE_DOM */

ext/dom/php_dom.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -846,7 +846,7 @@ PHP_MINIT_FUNCTION(dom)
846846
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "childElementCount", dom_parent_node_child_element_count, NULL);
847847
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "body", dom_html_document_body_read, dom_html_document_body_write);
848848
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "head", dom_html_document_head_read, NULL);
849-
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "title", dom_html_document_title_read, NULL);
849+
DOM_REGISTER_PROP_HANDLER(&dom_abstract_base_document_prop_handlers, "title", dom_html_document_title_read, dom_html_document_title_write);
850850
zend_hash_merge(&dom_abstract_base_document_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
851851
/* No need to register in &classes because this is an abstract class handler. */
852852

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
--TEST--
2+
DOM\Document::$title setter
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
echo "\n=== SVG namespaced test ===\n\n";
9+
10+
$dom = DOM\XMLDocument::createFromString('<root xmlns="http://www.w3.org/2000/svg"/>');
11+
$dom->title = "hello &amp; world";
12+
echo $dom->saveXML(), "\n";
13+
14+
$dom = DOM\XMLDocument::createFromString('<svg xmlns="http://www.w3.org/2000/svg"/>');
15+
$dom->title = "hello &amp; world";
16+
echo $dom->saveXML(), "\n";
17+
$dom->title = "";
18+
echo $dom->saveXML(), "\n";
19+
$dom->title = "test";
20+
echo $dom->saveXML(), "\n";
21+
22+
$dom = DOM\XMLDocument::createFromString('<svg:svg xmlns:svg="http://www.w3.org/2000/svg"/>');
23+
$dom->title = "test";
24+
echo $dom->saveXML(), "\n";
25+
var_dump($dom->documentElement->firstElementChild->prefix, $dom->documentElement->firstElementChild->namespaceURI);
26+
27+
$dom = DOM\XMLDocument::createFromString('<svg xmlns="http://www.w3.org/2000/svg">first node<div/></svg>');
28+
$dom->title = "test";
29+
echo $dom->saveXML(), "\n";
30+
$dom->documentElement->firstElementChild->remove();
31+
$dom->title = "test2";
32+
echo $dom->saveXML(), "\n";
33+
34+
echo "\n=== HTML namespaced test ===\n\n";
35+
36+
$dom = DOM\XMLDocument::createFromString('<root xmlns="http://www.w3.org/1999/xhtml"/>');
37+
$dom->title = "test";
38+
echo $dom->saveXML(), "\n";
39+
40+
$dom = DOM\XMLDocument::createFromString('<html xmlns="http://www.w3.org/1999/xhtml"/>');
41+
$dom->title = "test";
42+
echo $dom->saveXML(), "\n";
43+
44+
$dom = DOM\XMLDocument::createFromString('<html xmlns="http://www.w3.org/1999/xhtml"><foo/><head/></html>');
45+
$dom->title = "test";
46+
echo $dom->saveXML(), "\n";
47+
48+
$dom = DOM\XMLDocument::createFromString('<html xmlns="http://www.w3.org/1999/xhtml"><head><?ignore me?></head></html>');
49+
$dom->title = "test";
50+
echo $dom->saveXML(), "\n";
51+
52+
$dom = DOM\XMLDocument::createFromString('<html xmlns="http://www.w3.org/1999/xhtml"><head><?ignore me?><title>foo<div/></title></head></html>');
53+
$dom->title = "test";
54+
echo $dom->saveXML(), "\n";
55+
56+
echo "\n=== neither namespaced test ===\n\n";
57+
58+
$dom = DOM\XMLDocument::createEmpty();
59+
$dom->title = "";
60+
echo $dom->saveXML(), "\n";
61+
62+
$dom = DOM\XMLDocument::createFromString('<root/>');
63+
$dom->title = "test";
64+
echo $dom->saveXML(), "\n";
65+
66+
?>
67+
--EXPECT--
68+
=== SVG namespaced test ===
69+
70+
<?xml version="1.0" encoding="UTF-8"?>
71+
<root xmlns="http://www.w3.org/2000/svg"/>
72+
<?xml version="1.0" encoding="UTF-8"?>
73+
<svg xmlns="http://www.w3.org/2000/svg"><title>hello &amp;amp; world</title></svg>
74+
<?xml version="1.0" encoding="UTF-8"?>
75+
<svg xmlns="http://www.w3.org/2000/svg"><title></title></svg>
76+
<?xml version="1.0" encoding="UTF-8"?>
77+
<svg xmlns="http://www.w3.org/2000/svg"><title>test</title></svg>
78+
<?xml version="1.0" encoding="UTF-8"?>
79+
<svg:svg xmlns:svg="http://www.w3.org/2000/svg"><svg:title>test</svg:title></svg:svg>
80+
NULL
81+
string(26) "http://www.w3.org/2000/svg"
82+
<?xml version="1.0" encoding="UTF-8"?>
83+
<svg xmlns="http://www.w3.org/2000/svg"><title>test</title>first node<div/></svg>
84+
<?xml version="1.0" encoding="UTF-8"?>
85+
<svg xmlns="http://www.w3.org/2000/svg"><title>test2</title>first node<div/></svg>
86+
87+
=== HTML namespaced test ===
88+
89+
<?xml version="1.0" encoding="UTF-8"?>
90+
<root xmlns="http://www.w3.org/1999/xhtml"></root>
91+
<?xml version="1.0" encoding="UTF-8"?>
92+
<html xmlns="http://www.w3.org/1999/xhtml"></html>
93+
<?xml version="1.0" encoding="UTF-8"?>
94+
<html xmlns="http://www.w3.org/1999/xhtml"><foo></foo><head><title>test</title></head></html>
95+
<?xml version="1.0" encoding="UTF-8"?>
96+
<html xmlns="http://www.w3.org/1999/xhtml"><head><?ignore me?><title>test</title></head></html>
97+
<?xml version="1.0" encoding="UTF-8"?>
98+
<html xmlns="http://www.w3.org/1999/xhtml"><head><?ignore me?><title>test</title></head></html>
99+
100+
=== neither namespaced test ===
101+
102+
<?xml version="1.0" encoding="UTF-8"?>
103+
104+
<?xml version="1.0" encoding="UTF-8"?>
105+
<root/>

0 commit comments

Comments
 (0)