home
readme
diff
tree
note
docs
0commit 0bff1901284184c86da44aa48971c856d6d4fae3
1Author: Mason Wright <mason@lakefox.net>
2Date: Mon Jul 7 10:53:45 2025 -0600
3
4 Added 2 cases for has
5
6diff --git a/src/grim.cc b/src/grim.cc
7index 438e45d..33fd5b7 100755
8--- a/src/grim.cc
9+++ b/src/grim.cc
10@@ -5,6 +5,7 @@
11 #include <stdexcept>
12 #include <cctype>
13 #include <string_view>
14+#include <span>
15
16 // Constructor
17
18@@ -632,7 +633,9 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
19 }
20
21 // Find the index of the child relative to it's parent
22-
23+
24+ // the children variable is the PARENTS CHILDREN the the current node's children
25+ // if you want those, node->children
26 const std::vector<std::unique_ptr<Node>>& children = (node->parent != nullptr) ? node->parent->children : EMPTY_NODE_CHILDREN_VECTOR;
27 size_t childIndex = 0;
28 if (children.size() > 1) {
29@@ -707,7 +710,7 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
30 // we need to find what the selector is targeting
31 std::string value = p.substr(5,p.length()-6);
32 auto innerParts = parseSelectorParts(value);
33- bool subMatch = false;
34+ bool found = false;
35
36 for (auto ip : innerParts) {
37 // if the selector was :has(+ p, div, > video)
38@@ -715,7 +718,42 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
39 // these differ from how we parse the parts above
40 // if the first item in a selector is not a +~> then
41 // it is a decendent otherwise they are the same
42+
43+ // We need to have a 2d with the contents of ip without the first
44+ // selector if it is't a decendent combinator so we do that here
45+ std::vector<std::string> sliceOfParts(ip.begin()+1, ip.end());
46+ std::vector<std::vector<std::string>> slicedParts;
47+ slicedParts.push_back(sliceOfParts);
48+
49+ if (ip[0] == ">") {
50+ // The child combinator only checks direct children so we
51+ // can loop through and if we find one that matches ip[1:]
52+ // (remove the ">") we can stop and return
53+ for (const auto& c : node->children) {
54+
55+ found = testSelector(c.get(), slicedParts);
56+ if (found) {
57+ break;
58+ }
59+ }
60+ } else if (ip[0] == "+") {
61+ if (children.size() > 1) {
62+ // If there isn't a prevous sibling then it cannot match
63+ if (childIndex < children.size()-1) {
64+ // In this selector we just need to get the direct sibling and test it
65+ found = testSelector(children[childIndex+1].get(), slicedParts);
66+ } else {
67+ // if the element is the last there is no way it could have one
68+ found = false;
69+ }
70+ }
71+ }
72+
73+ if (found) {
74+ break;
75+ }
76 }
77+ matched = found;
78 }
79 // If there are any pseudo elements we will just skip them
80 // with the transformers being replaced with pseudo elements
81@@ -745,7 +783,7 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
82 }
83
84 if (symbol == ">") {
85- // child cominator
86+ // child combinator
87 // We are just testing the parent node here
88 matched = testSelector(node->parent, {trimmed});
89 } else if (symbol == "+") {
90diff --git a/tests/css_selector.cc b/tests/css_selector.cc
91index 90725cc..039e893 100644
92--- a/tests/css_selector.cc
93+++ b/tests/css_selector.cc
94@@ -222,10 +222,20 @@ TEST_CASE("testSelector", "[CSS]") {
95 return testSelector(input, parseSelectorParts(":not(:is(h1, html, div))"));
96 };
97 }
98- SECTION("testSelector can match a :has() selector") {
99+ SECTION("testSelector can match a :has() selector with a child combinator") {
100+ auto body = document->children[0]->children[1].get();
101+ REQUIRE(testSelector(body, parseSelectorParts("body:has(> h1)")));
102+ REQUIRE_FALSE(testSelector(body, parseSelectorParts("body:has(> h1:required)")));
103+ }
104+ SECTION("testSelector can match a :has() selector with a next-sibling combinator") {
105 auto h1 = document->children[0]->children[1]->children[0].get();
106 REQUIRE(testSelector(h1, parseSelectorParts("h1:has(+ p)")));
107 REQUIRE_FALSE(testSelector(h1, parseSelectorParts("h1:has(+ p:required)")));
108+
109+
110+ BENCHMARK("has with a next sibling combinator") {
111+ return testSelector(h1, parseSelectorParts("h1:has(+ p)"));
112+ };
113 }
114 // make for:
115 // pseudo elements