Skip to content

Commit 47feb57

Browse files
committed
Support named items in dimension handling for HTMLCollection
Closes phpGH-13937.
1 parent b3f820b commit 47feb57

File tree

7 files changed

+193
-22
lines changed

7 files changed

+193
-22
lines changed

ext/dom/html_collection.c

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,29 +21,33 @@
2121
#include "php.h"
2222
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
2323
#include "php_dom.h"
24+
#include "nodelist.h"
25+
#include "html_collection.h"
2426
#include "namespace_compat.h"
2527

28+
typedef struct _dom_named_item {
29+
dom_object *context_intern;
30+
xmlNodePtr node;
31+
} dom_named_item;
32+
2633
/* https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem-key */
27-
PHP_METHOD(DOM_HTMLCollection, namedItem)
34+
static dom_named_item dom_html_collection_named_item(zend_string *key, zend_object *zobj)
2835
{
29-
zend_string *key;
30-
ZEND_PARSE_PARAMETERS_START(1, 1)
31-
Z_PARAM_PATH_STR(key)
32-
ZEND_PARSE_PARAMETERS_END();
36+
dom_named_item ret = {NULL, NULL};
3337

3438
/* 1. If key is the empty string, return null. */
3539
if (ZSTR_LEN(key) == 0) {
36-
RETURN_NULL();
40+
return ret;
3741
}
3842

39-
dom_object *intern = Z_DOMOBJ_P(ZEND_THIS);
43+
dom_object *intern = php_dom_obj_from_obj(zobj);
4044
dom_nnodemap_object *objmap = intern->ptr;
4145

4246
/* 2. Return the first element in the collection for which at least one of the following is true: */
4347
xmlNodePtr basep = dom_object_get_node(objmap->baseobj);
4448
if (basep != NULL) {
4549
int cur = 0;
46-
int next = cur;
50+
int next = cur; /* not +1, otherwise we skip the first candidate */
4751
xmlNodePtr candidate = basep->children;
4852
while (candidate != NULL) {
4953
candidate = dom_get_elements_by_tag_name_ns_raw(basep, candidate, objmap->ns, objmap->local, objmap->local_lower, &cur, next);
@@ -55,20 +59,85 @@ PHP_METHOD(DOM_HTMLCollection, namedItem)
5559

5660
/* it has an ID which is key; */
5761
if ((attr = xmlHasNsProp(candidate, BAD_CAST "id", NULL)) != NULL && dom_compare_value(attr, BAD_CAST ZSTR_VAL(key))) {
58-
DOM_RET_OBJ(candidate, objmap->baseobj);
59-
return;
62+
ret.context_intern = objmap->baseobj;
63+
ret.node = candidate;
64+
return ret;
6065
}
6166
/* it is in the HTML namespace and has a name attribute whose value is key; */
6267
else if (php_dom_ns_is_fast(candidate, php_dom_ns_is_html_magic_token)) {
6368
if ((attr = xmlHasNsProp(candidate, BAD_CAST "name", NULL)) != NULL && dom_compare_value(attr, BAD_CAST ZSTR_VAL(key))) {
64-
DOM_RET_OBJ(candidate, objmap->baseobj);
65-
return;
69+
ret.context_intern = objmap->baseobj;
70+
ret.node = candidate;
71+
return ret;
6672
}
6773
}
6874

6975
next = cur + 1;
7076
}
7177
}
78+
79+
return ret;
80+
}
81+
82+
static void dom_html_collection_named_item_into_zval(zval *return_value, zend_string *key, zend_object *zobj)
83+
{
84+
dom_named_item named_item = dom_html_collection_named_item(key, zobj);
85+
if (named_item.node != NULL) {
86+
DOM_RET_OBJ(named_item.node, named_item.context_intern);
87+
} else {
88+
RETURN_NULL();
89+
}
90+
}
91+
92+
PHP_METHOD(DOM_HTMLCollection, namedItem)
93+
{
94+
zend_string *key;
95+
ZEND_PARSE_PARAMETERS_START(1, 1)
96+
Z_PARAM_STR(key)
97+
ZEND_PARSE_PARAMETERS_END();
98+
dom_html_collection_named_item_into_zval(return_value, key, Z_OBJ_P(ZEND_THIS));
99+
}
100+
101+
zval *dom_html_collection_read_dimension(zend_object *object, zval *offset, int type, zval *rv)
102+
{
103+
if (UNEXPECTED(!offset)) {
104+
zend_throw_error(NULL, "Cannot append to %s", ZSTR_VAL(object->ce->name));
105+
return NULL;
106+
}
107+
108+
dom_nodelist_dimension_index index = dom_modern_nodelist_get_index(offset);
109+
if (UNEXPECTED(index.type == DOM_NODELIST_DIM_ILLEGAL)) {
110+
zend_illegal_container_offset(object->ce->name, offset, type);
111+
return NULL;
112+
}
113+
114+
if (index.type == DOM_NODELIST_DIM_STRING) {
115+
dom_html_collection_named_item_into_zval(rv, index.str, object);
116+
} else {
117+
ZEND_ASSERT(index.type == DOM_NODELIST_DIM_LONG);
118+
php_dom_nodelist_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, index.lval, rv);
119+
}
120+
121+
return rv;
122+
}
123+
124+
int dom_html_collection_has_dimension(zend_object *object, zval *member, int check_empty)
125+
{
126+
/* If it exists, it cannot be empty because nodes aren't empty. */
127+
ZEND_IGNORE_VALUE(check_empty);
128+
129+
dom_nodelist_dimension_index index = dom_modern_nodelist_get_index(member);
130+
if (UNEXPECTED(index.type == DOM_NODELIST_DIM_ILLEGAL)) {
131+
zend_illegal_container_offset(object->ce->name, member, BP_VAR_IS);
132+
return 0;
133+
}
134+
135+
if (index.type == DOM_NODELIST_DIM_STRING) {
136+
return dom_html_collection_named_item(index.str, object).node != NULL;
137+
} else {
138+
ZEND_ASSERT(index.type == DOM_NODELIST_DIM_LONG);
139+
return index.lval >= 0 && index.lval < php_dom_get_nodelist_length(php_dom_obj_from_obj(object));
140+
}
72141
}
73142

74143
#endif

ext/dom/html_collection.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
| [email protected] so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Authors: Niels Dossche <[email protected]> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#ifndef PHP_HTML_COLLECTION_H
18+
#define PHP_HTML_COLLECTION_H
19+
20+
zval *dom_html_collection_read_dimension(zend_object *object, zval *offset, int type, zval *rv);
21+
int dom_html_collection_has_dimension(zend_object *object, zval *member, int check_empty);
22+
23+
#endif

ext/dom/nodelist.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ ZEND_METHOD(DOMNodeList, getIterator)
250250
zend_create_internal_iterator_zval(return_value, ZEND_THIS);
251251
}
252252

253-
dom_nodelist_dimension_index dom_modern_nodelist_get_index(zval *offset)
253+
dom_nodelist_dimension_index dom_modern_nodelist_get_index(const zval *offset)
254254
{
255255
dom_nodelist_dimension_index ret;
256256

ext/dom/nodelist.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ typedef struct _dom_nodelist_dimension_index {
3333

3434
void php_dom_nodelist_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value);
3535
int php_dom_get_nodelist_length(dom_object *obj);
36-
dom_nodelist_dimension_index dom_modern_nodelist_get_index(zval *offset);
36+
dom_nodelist_dimension_index dom_modern_nodelist_get_index(const zval *offset);
3737
zval *dom_modern_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv);
3838
int dom_modern_nodelist_has_dimension(zend_object *object, zval *member, int check_empty);
3939

ext/dom/php_dom.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
2525
#include "php_dom.h"
2626
#include "nodelist.h"
27+
#include "html_collection.h"
2728
#include "namespace_compat.h"
2829
#include "internal_helpers.h"
2930
#include "php_dom_arginfo.h"
@@ -90,6 +91,7 @@ static zend_object_handlers dom_nnodemap_object_handlers;
9091
static zend_object_handlers dom_nodelist_object_handlers;
9192
static zend_object_handlers dom_modern_nnodemap_object_handlers;
9293
static zend_object_handlers dom_modern_nodelist_object_handlers;
94+
static zend_object_handlers dom_html_collection_object_handlers;
9395
static zend_object_handlers dom_object_namespace_node_handlers;
9496
static zend_object_handlers dom_modern_domimplementation_object_handlers;
9597
#ifdef LIBXML_XPATH_ENABLED
@@ -715,6 +717,10 @@ PHP_MINIT_FUNCTION(dom)
715717
dom_modern_nodelist_object_handlers.read_dimension = dom_modern_nodelist_read_dimension;
716718
dom_modern_nodelist_object_handlers.has_dimension = dom_modern_nodelist_has_dimension;
717719

720+
memcpy(&dom_html_collection_object_handlers, &dom_modern_nodelist_object_handlers, sizeof(zend_object_handlers));
721+
dom_html_collection_object_handlers.read_dimension = dom_html_collection_read_dimension;
722+
dom_html_collection_object_handlers.has_dimension = dom_html_collection_has_dimension;
723+
718724
memcpy(&dom_object_namespace_node_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
719725
dom_object_namespace_node_handlers.offset = XtOffsetOf(dom_object_namespace_node, dom.std);
720726
dom_object_namespace_node_handlers.free_obj = dom_object_namespace_node_free_storage;
@@ -927,7 +933,7 @@ PHP_MINIT_FUNCTION(dom)
927933

928934
dom_html_collection_class_entry = register_class_DOM_HTMLCollection(zend_ce_aggregate, zend_ce_countable);
929935
dom_html_collection_class_entry->create_object = dom_nnodemap_objects_new;
930-
dom_html_collection_class_entry->default_object_handlers = &dom_modern_nodelist_object_handlers;
936+
dom_html_collection_class_entry->default_object_handlers = &dom_html_collection_object_handlers;
931937
dom_html_collection_class_entry->get_iterator = php_dom_get_iterator;
932938

933939
zend_hash_add_new_ptr(&classes, dom_html_collection_class_entry->name, &dom_nodelist_prop_handlers);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
HTMLCollection::namedItem() and dimension handling for named accesses
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = DOM\XMLDocument::createFromString('<root/>');
9+
10+
try {
11+
$dom->getElementsByTagName('root')[][1] = 1;
12+
} catch (Error $e) {
13+
echo $e->getMessage(), "\n";
14+
}
15+
16+
try {
17+
$dom->getElementsByTagName('root')[true];
18+
} catch (Error $e) {
19+
echo $e->getMessage(), "\n";
20+
}
21+
22+
try {
23+
isset($dom->getElementsByTagName('root')[true]);
24+
} catch (Error $e) {
25+
echo $e->getMessage(), "\n";
26+
}
27+
28+
?>
29+
--EXPECT--
30+
Cannot append to DOM\HTMLCollection
31+
Cannot access offset of type bool on DOM\HTMLCollection
32+
Cannot access offset of type bool in isset or empty

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

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,61 @@ $xml = <<<XML
2222
XML;
2323

2424
$dom = DOM\XMLDocument::createFromString($xml);
25-
var_dump($dom->getElementsByTagName('node')->namedItem('foo')?->textContent);
26-
var_dump($dom->getElementsByTagName('node')->namedItem('')?->textContent);
27-
var_dump($dom->getElementsByTagName('node')->namedItem('does not exist')?->textContent);
28-
var_dump($dom->getElementsByTagName('node')->namedItem('wrong')?->textContent);
29-
var_dump($dom->getElementsByTagName('node')->namedItem('bar')?->textContent);
30-
var_dump($dom->getElementsByTagName('x')->namedItem('foo')?->textContent);
31-
var_dump($dom->getElementsByTagName('x')->namedItem('footest')?->textContent);
25+
26+
function test($obj, $name) {
27+
echo "--- Query \"$name\" ---\n";
28+
var_dump($obj->namedItem($name)?->textContent);
29+
var_dump($obj[$name]?->textContent);
30+
var_dump(isset($obj[$name]));
31+
32+
// Search to check for dimension access consistency
33+
$node = $obj[$name];
34+
if ($node) {
35+
$found = false;
36+
for ($i = 0; $i < $obj->length && !$found; $i++) {
37+
$found = $obj[$i] === $node;
38+
}
39+
if (!$found) {
40+
throw new Error('inconsistency in dimension access');
41+
}
42+
}
43+
}
44+
45+
test($dom->getElementsByTagName('node'), 'foo');
46+
test($dom->getElementsByTagName('node'), '');
47+
test($dom->getElementsByTagName('node'), 'does not exist');
48+
test($dom->getElementsByTagName('node'), 'wrong');
49+
test($dom->getElementsByTagName('node'), 'bar');
50+
test($dom->getElementsByTagName('x'), 'foo');
51+
test($dom->getElementsByTagName('x'), 'footest');
3252

3353
?>
3454
--EXPECT--
55+
--- Query "foo" ---
3556
string(1) "5"
57+
string(1) "5"
58+
bool(true)
59+
--- Query "" ---
60+
NULL
3661
NULL
62+
bool(false)
63+
--- Query "does not exist" ---
3764
NULL
65+
NULL
66+
bool(false)
67+
--- Query "wrong" ---
68+
string(1) "4"
3869
string(1) "4"
70+
bool(true)
71+
--- Query "bar" ---
72+
string(12) "with html ns"
3973
string(12) "with html ns"
74+
bool(true)
75+
--- Query "foo" ---
4076
string(1) "2"
77+
string(1) "2"
78+
bool(true)
79+
--- Query "footest" ---
80+
string(13) "2 with entity"
4181
string(13) "2 with entity"
82+
bool(true)

0 commit comments

Comments
 (0)