Skip to content

Commit 72a5407

Browse files
committed
Implement DOM\Document::$title setter
1 parent 024e9bc commit 72a5407

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
@@ -66,6 +66,7 @@ zend_result dom_html_document_body_read(dom_object *obj, zval *retval);
6666
zend_result dom_html_document_body_write(dom_object *obj, zval *newval);
6767
zend_result dom_html_document_head_read(dom_object *obj, zval *retval);
6868
zend_result dom_html_document_title_read(dom_object *obj, zval *retval);
69+
zend_result dom_html_document_title_write(dom_object *obj, zval *newval);
6970

7071
/* documenttype properties */
7172
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) {
@@ -1493,11 +1493,28 @@ static const xmlNode *dom_get_title_element(const xmlDoc *doc)
14931493
return node;
14941494
}
14951495

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

15021519
if (root == NULL) {
15031520
ZVAL_EMPTY_STRING(retval);
@@ -1509,15 +1526,9 @@ zend_result dom_html_document_title_read(dom_object *obj, zval *retval)
15091526
/* 1. If the document element is an SVG svg element,
15101527
* then let value be the child text content of the first SVG title element that is a child of the document element. */
15111528
if (php_dom_ns_is_fast(root, php_dom_ns_is_svg_magic_token) && xmlStrEqual(root->name, BAD_CAST "svg")) {
1512-
const xmlNode *cur = root->children;
1513-
1514-
while (cur != NULL) {
1515-
if (cur->type == XML_ELEMENT_NODE
1516-
&& php_dom_ns_is_fast(cur, php_dom_ns_is_svg_magic_token) && xmlStrEqual(cur->name, BAD_CAST "title")) {
1517-
value = dom_get_child_text_content(cur);
1518-
break;
1519-
}
1520-
cur = cur->next;
1529+
const xmlNode *title = dom_get_svg_title_element(root);
1530+
if (title != NULL) {
1531+
value = dom_get_child_text_content(title);
15211532
}
15221533
} else {
15231534
/* 2. Otherwise, let value be the child text content of the title element,
@@ -1537,4 +1548,95 @@ zend_result dom_html_document_title_read(dom_object *obj, zval *retval)
15371548
return SUCCESS;
15381549
}
15391550

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

ext/dom/php_dom.c

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

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)