Skip to content

Commit fca2bbb

Browse files
committed
1 parent 46213f8 commit fca2bbb

File tree

9 files changed

+276
-4
lines changed

9 files changed

+276
-4
lines changed

ext/dom/element.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,44 @@ PHP_METHOD(Dom_Element, getElementsByTagName)
842842
}
843843
/* }}} end dom_element_get_elements_by_tag_name */
844844

845+
PHP_METHOD(Dom_Element, getElementsByClassName)
846+
{
847+
dom_object *intern, *namednode;
848+
zend_string *class_names;
849+
850+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "P", &class_names) == FAILURE) {
851+
RETURN_THROWS();
852+
}
853+
854+
if (ZSTR_LEN(class_names) > INT_MAX) {
855+
zend_argument_value_error(1, "is too long");
856+
RETURN_THROWS();
857+
}
858+
859+
DOM_GET_THIS_INTERN(intern);
860+
861+
object_init_ex(return_value, dom_html_collection_class_entry);
862+
namednode = Z_DOMOBJ_P(return_value);
863+
864+
HashTable *token_set;
865+
ALLOC_HASHTABLE(token_set);
866+
zend_hash_init(token_set, 0, NULL, NULL, false);
867+
dom_ordered_set_parser(token_set, ZSTR_VAL(class_names), intern->document->quirks_mode == PHP_LIBXML_QUIRKS);
868+
869+
if (zend_hash_num_elements(token_set) == 0) {
870+
php_dom_create_obj_map(intern, namednode, NULL, NULL, NULL, &php_dom_obj_map_noop);
871+
872+
zend_hash_destroy(token_set);
873+
FREE_HASHTABLE(token_set);
874+
} else {
875+
php_dom_create_obj_map(intern, namednode, NULL, NULL, NULL, &php_dom_obj_map_by_class_name);
876+
877+
dom_nnodemap_object *map = namednode->ptr;
878+
map->array = token_set;
879+
map->release_array = true;
880+
}
881+
}
882+
845883
/* should_free_result must be initialized to false */
846884
static const xmlChar *dom_get_attribute_ns(dom_object *intern, xmlNodePtr elemp, const char *uri, size_t uri_len, const char *name, bool *should_free_result)
847885
{

ext/dom/obj_map.c

Lines changed: 96 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 "obj_map.h"
27+
#include "token_list.h"
2728

2829
static zend_always_inline void objmap_cache_release_cached_obj(dom_nnodemap_object *objmap)
2930
{
@@ -40,6 +41,30 @@ static zend_always_inline void reset_objmap_cache(dom_nnodemap_object *objmap)
4041
objmap->cached_length = -1;
4142
}
4243

44+
static bool dom_matches_class_name(const dom_nnodemap_object *map, const xmlNode *nodep)
45+
{
46+
bool ret = false;
47+
48+
if (nodep->type == XML_ELEMENT_NODE) {
49+
xmlAttrPtr classes = xmlHasNsProp(nodep, BAD_CAST "class", NULL);
50+
if (classes != NULL) {
51+
bool should_free;
52+
xmlChar *value = php_libxml_attr_value(classes, &should_free);
53+
54+
bool quirks = map->baseobj->document->quirks_mode == PHP_LIBXML_QUIRKS;
55+
if (dom_ordered_set_all_contained(map->array, (const char *) value, quirks)) {
56+
ret = true;
57+
}
58+
59+
if (should_free) {
60+
xmlFree(value);
61+
}
62+
}
63+
}
64+
65+
return ret;
66+
}
67+
4368
/**************************
4469
* === Length methods === *
4570
**************************/
@@ -106,6 +131,24 @@ static zend_long dom_map_get_by_tag_name_length(dom_nnodemap_object *map)
106131
return count;
107132
}
108133

134+
static zend_long dom_map_get_by_class_name_length(dom_nnodemap_object *map)
135+
{
136+
xmlNodePtr nodep = dom_object_get_node(map->baseobj);
137+
zend_long count = 0;
138+
if (nodep) {
139+
xmlNodePtr basep = nodep;
140+
nodep = php_dom_first_child_of_container_node(basep);
141+
142+
while (nodep != NULL) {
143+
if (dom_matches_class_name(map, nodep)) {
144+
count++;
145+
}
146+
nodep = php_dom_next_in_tree_order(nodep, basep);
147+
}
148+
}
149+
return count;
150+
}
151+
109152
static zend_long dom_map_get_zero_length(dom_nnodemap_object *map)
110153
{
111154
return 0;
@@ -276,6 +319,10 @@ static void dom_map_collection_named_item_elements_iter(dom_nnodemap_object *map
276319
}
277320
}
278321

322+
static void dom_map_collection_named_item_null(dom_nnodemap_object *map, php_dom_obj_map_collection_iter *iter)
323+
{
324+
}
325+
279326
static void dom_map_get_by_tag_name_item(dom_nnodemap_object *map, zend_long index, zval *return_value)
280327
{
281328
xmlNodePtr nodep = dom_object_get_node(map->baseobj);
@@ -292,12 +339,50 @@ static void dom_map_get_by_tag_name_item(dom_nnodemap_object *map, zend_long ind
292339
}
293340
}
294341

342+
static void dom_map_get_by_class_name_item(dom_nnodemap_object *map, zend_long index, zval *return_value)
343+
{
344+
xmlNodePtr nodep = dom_object_get_node(map->baseobj);
345+
xmlNodePtr itemnode = NULL;
346+
if (nodep && index >= 0) {
347+
xmlNodePtr basep = nodep;
348+
dom_node_idx_pair start_point = dom_obj_map_get_start_point(map, nodep, index);
349+
if (start_point.node) {
350+
itemnode = php_dom_next_in_tree_order(start_point.node, basep);
351+
} else {
352+
itemnode = php_dom_first_child_of_container_node(nodep);
353+
}
354+
355+
do {
356+
--start_point.index;
357+
while (itemnode != NULL && !dom_matches_class_name(map, itemnode)) {
358+
itemnode = php_dom_next_in_tree_order(itemnode, basep);
359+
}
360+
} while (start_point.index > 0 && itemnode);
361+
}
362+
dom_ret_node_to_zobj(map, itemnode, return_value);
363+
if (itemnode) {
364+
dom_map_cache_obj(map, itemnode, index, return_value);
365+
}
366+
}
367+
295368
static void dom_map_collection_named_item_by_tag_name_iter(dom_nnodemap_object *map, php_dom_obj_map_collection_iter *iter)
296369
{
297370
iter->candidate = dom_get_elements_by_tag_name_ns_raw(iter->basep, iter->candidate, map->ns, map->local, map->local_lower, &iter->cur, iter->next);
298371
iter->next = iter->cur + 1;
299372
}
300373

374+
static void dom_map_collection_named_item_by_class_name_iter(dom_nnodemap_object *map, php_dom_obj_map_collection_iter *iter)
375+
{
376+
xmlNodePtr basep = iter->basep;
377+
xmlNodePtr nodep = iter->candidate ? php_dom_next_in_tree_order(iter->candidate, basep) : php_dom_first_child_of_container_node(basep);
378+
379+
while (nodep != NULL && !dom_matches_class_name(map, nodep)) {
380+
nodep = php_dom_next_in_tree_order(nodep, basep);
381+
}
382+
383+
iter->candidate = nodep;
384+
}
385+
301386
static void dom_map_get_null_item(dom_nnodemap_object *map, zend_long index, zval *return_value)
302387
{
303388
RETURN_NULL();
@@ -478,6 +563,16 @@ const php_dom_obj_map_handler php_dom_obj_map_by_tag_name = {
478563
.nameless = true,
479564
};
480565

566+
const php_dom_obj_map_handler php_dom_obj_map_by_class_name = {
567+
.length = dom_map_get_by_class_name_length,
568+
.get_item = dom_map_get_by_class_name_item,
569+
.get_ns_named_item = dom_map_get_ns_named_item_null,
570+
.has_ns_named_item = dom_map_has_ns_named_item_null,
571+
.collection_named_item_iter = dom_map_collection_named_item_by_class_name_iter,
572+
.use_cache = true,
573+
.nameless = true,
574+
};
575+
481576
const php_dom_obj_map_handler php_dom_obj_map_child_nodes = {
482577
.length = dom_map_get_nodes_length,
483578
.get_item = dom_map_get_nodes_item,
@@ -533,7 +628,7 @@ const php_dom_obj_map_handler php_dom_obj_map_noop = {
533628
.get_item = dom_map_get_null_item,
534629
.get_ns_named_item = dom_map_get_ns_named_item_null,
535630
.has_ns_named_item = dom_map_has_ns_named_item_null,
536-
.collection_named_item_iter = NULL,
631+
.collection_named_item_iter = dom_map_collection_named_item_null,
537632
.use_cache = false,
538633
.nameless = true,
539634
};

ext/dom/obj_map.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ zend_long php_dom_get_nodelist_length(dom_object *obj);
6363

6464
extern const php_dom_obj_map_handler php_dom_obj_map_attributes;
6565
extern const php_dom_obj_map_handler php_dom_obj_map_by_tag_name;
66+
extern const php_dom_obj_map_handler php_dom_obj_map_by_class_name;
6667
extern const php_dom_obj_map_handler php_dom_obj_map_child_elements;
6768
extern const php_dom_obj_map_handler php_dom_obj_map_child_nodes;
6869
extern const php_dom_obj_map_handler php_dom_obj_map_nodeset;

ext/dom/php_dom.stub.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,6 +1659,7 @@ public function removeAttributeNode(Attr $attr) : Attr {}
16591659

16601660
public function getElementsByTagName(string $qualifiedName): HTMLCollection {}
16611661
public function getElementsByTagNameNS(?string $namespace, string $localName): HTMLCollection {}
1662+
public function getElementsByClassName(string $classNames): HTMLCollection {}
16621663

16631664
public function insertAdjacentElement(AdjacentPosition $where, Element $element): ?Element {}
16641665
public function insertAdjacentText(AdjacentPosition $where, string $data): void {}
@@ -1986,6 +1987,8 @@ abstract class Document extends Node implements ParentNode
19861987
public function getElementsByTagName(string $qualifiedName): HTMLCollection {}
19871988
/** @implementation-alias Dom\Element::getElementsByTagNameNS */
19881989
public function getElementsByTagNameNS(?string $namespace, string $localName): HTMLCollection {}
1990+
/** @implementation-alias Dom\Element::getElementsByClassName */
1991+
public function getElementsByClassName(string $classNames): HTMLCollection {}
19891992

19901993
public function createElement(string $localName): Element {}
19911994
public function createElementNS(?string $namespace, string $qualifiedName): Element {}

ext/dom/php_dom_arginfo.h

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
Dom\Element::getElementsByClassName()
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = Dom\HTMLDocument::createFromString(<<<HTML
9+
<div class=" foo bar ">
10+
<p class="bar">
11+
<p class="bar" name="here">1</p>
12+
<p name="here">2</p>
13+
<p name="here">3</p>
14+
<p class="bar" name="here">4</p>
15+
</p>
16+
<b class="foo bars"></b>
17+
</div>
18+
HTML, LIBXML_NOERROR);
19+
$collection = $dom->documentElement->getElementsByClassName("bar");
20+
21+
echo "There are {$dom->getElementsByClassName("foo \n bar")->count()} items in the document in total that have both \"foo\" and \"bar\"\n";
22+
23+
echo "There are {$collection->count()} \"bar\" items\n";
24+
25+
foreach ($collection as $key => $node) {
26+
var_dump($key, $node->tagName);
27+
var_dump($node === $collection->item($key));
28+
}
29+
30+
var_dump($collection->namedItem("here")->textContent);
31+
32+
?>
33+
--EXPECT--
34+
There are 1 items in the document in total that have both "foo" and "bar"
35+
There are 4 "bar" items
36+
int(0)
37+
string(3) "DIV"
38+
bool(false)
39+
int(1)
40+
string(1) "P"
41+
bool(false)
42+
string(1) "1"
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
Dom\Element::getElementsByClassName() empty class names
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
$dom = Dom\HTMLDocument::createFromString(<<<HTML
9+
<div class=" foo bar ">
10+
<p id="child"></p>
11+
</div>
12+
HTML, LIBXML_NOERROR);
13+
14+
$collection = $dom->documentElement->getElementsByClassName("");
15+
var_dump($collection->count());
16+
17+
foreach ($collection as $node) {
18+
throw new Error("unreachable");
19+
}
20+
21+
var_dump($dom->getElementsByClassName(" ")->count());
22+
var_dump($dom->getElementsByClassName("\t")->count());
23+
var_dump($dom->getElementsByClassName("\t\n\f\v")->count());
24+
var_dump($dom->getElementsByClassName("\t\n\f\v")->namedItem("cjild"));
25+
26+
?>
27+
--EXPECT--
28+
int(0)
29+
int(0)
30+
int(0)
31+
int(0)
32+
NULL

0 commit comments

Comments
 (0)