home
readme
diff
tree
note
docs
0commit 95e6c70d23e99ffcf70e5bbe12503496e5d8f232
1Author: Mason Wright <mason@lakefox.net>
2Date: Sun Jun 29 20:21:34 2025 -0600
3
4 Have a rough testSelector, starting testing
5
6diff --git a/Makefile b/Makefile
7index 88a1807..31db22a 100644
8--- a/Makefile
9+++ b/Makefile
10@@ -23,16 +23,16 @@ build/parser.o: src/parser.cc include/parser.h | build
11 # --- Tests ---
12
13 run_tests: build/html_parser_test
14- ./build/html_parser_test
15+ build/html_parser_test
16
17-build/css_selector_test: tests/css_selector.cc build/grim.o build/catch.o | build
18- ${CC} ${CCFLAGS} -Iinclude/ ./tests/css_selector.cc ./build/grim.o ./build/catch.o -o ./build/css_selector_test
19+build/css_selector_test: tests/css_selector.cc build/grim.o build/catch.o build/parser.o | build
20+ ${CC} ${CCFLAGS} -Iinclude/ tests/css_selector.cc build/grim.o build/catch.o build/parser.o -o build/css_selector_test
21
22 build/html_parser_test: tests/html_parser.cc build/grim.o build/parser.o build/catch.o | build
23- ${CC} ${CCFLAGS} -Iinclude/ ./tests/html_parser.cc ./build/grim.o ./build/parser.o ./build/catch.o -o ./build/html_parser_test
24+ ${CC} ${CCFLAGS} -Iinclude/ tests/html_parser.cc build/grim.o build/parser.o build/catch.o -o build/html_parser_test
25
26 build/catch.o: ./include/catch_amalgamated.cpp ./include/catch_amalgamated.hpp | build
27- ${CC} ${CCFLAGS} -Iinclude/ -c ./include/catch_amalgamated.cpp -o build/catch.o
28+ ${CC} ${CCFLAGS} -Iinclude/ -c include/catch_amalgamated.cpp -o build/catch.o
29
30
31 # --- Tools ---
32diff --git a/include/grim.h b/include/grim.h
33index 638c4d2..edae26d 100644
34--- a/include/grim.h
35+++ b/include/grim.h
36@@ -98,4 +98,6 @@ public:
37 */
38 std::vector<std::vector<std::string>> parseSelectorParts(std::string);
39
40+bool testSelector(Node*, std::vector<std::vector<std::string>>);
41+
42 #endif // NODE_H
43diff --git a/src/grim.cc b/src/grim.cc
44index 2cdc017..f674f6f 100755
45--- a/src/grim.cc
46+++ b/src/grim.cc
47@@ -264,11 +264,24 @@ std::vector<std::vector<std::string>> parseSelectorParts(std::string selector) {
48 if (nesting == 0 && !current.empty()) {
49 // !ISSUE: Missing space
50 if (s == ':' || s == '[' || s == ',' || s == '#' || s == '.') {
51- buffer.push_back(trimSpace(current));
52+ // We convert any : selectors (like :checked) to [checked]
53+ // because we store every thing as attributes on the Node
54+ // when test selector is ran it will convert all its attributes
55+ // to [checked] or [href="url"]
56+ // so we can easily compare the two
57+ std::string trimmed = trimSpace(current);
58+ if (trimmed[0] == ':' && trimmed.back() != ')') {
59+ trimmed = "[" +trimmed.substr(1)+"]";
60+ }
61+ buffer.push_back(trimmed);
62 current = s;
63 } else
64 if (s == '>' || s == '+' || s == '~' || s == ' ') {
65- buffer.push_back(trimSpace(current));
66+ std::string trimmed = trimSpace(current);
67+ if (trimmed[0] == ':' && trimmed.back() != ')') {
68+ trimmed = "[" +trimmed.substr(1)+"]";
69+ }
70+ buffer.push_back(trimmed);
71 buffer.push_back("");
72 buffer.back() += s;
73 current = "";
74@@ -280,9 +293,12 @@ std::vector<std::vector<std::string>> parseSelectorParts(std::string selector) {
75 }
76
77 if ((s == ',' && nesting == 0) || e == sl-1) {
78- //std::cout << selector.substr(start, e-start+1) << std::endl;
79 if (!current.empty() && current != ",") {
80- buffer.push_back(trimSpace(current));
81+ std::string trimmed = trimSpace(current);
82+ if (trimmed[0] == ':' && trimmed.back() != ')') {
83+ trimmed = "[" +trimmed.substr(1)+"]";
84+ }
85+ buffer.push_back(trimmed);
86 }
87 parts.push_back(buffer);
88 buffer.clear();
89@@ -391,21 +407,18 @@ class StyleHandler {
90 // we need to pull the selectors from each candidate then run testSelector on it
91 }
92 };
93-/*
94+
95 bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
96 // By the time we get here, we know the right most selector some what matches
97 // so now if it is a compound selector we should see if the parent('s) some what match
98- if (selector == "*") {
99- return true;
100- }
101-
102
103+ // !TODO: Add support for *
104 if (selector.size() > 1) {
105 // If a selector is made up of several different selectors
106- // ie h1, h2, h3
107+ // ie {{"h1"}, {"h2"}, {"h3"}}
108 // run each selector and return on a match
109 for (auto s : selector) {
110- bool match = testSelector({s});
111+ bool match = testSelector(node, {s});
112 if (match) {
113 return true;
114 }
115@@ -414,24 +427,38 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
116 }
117
118
119+ std::vector<std::string> trimmed;
120 std::vector<std::string> parts;
121+ std::string symbol;
122+ bool symbolFound = false;
123
124- for (size_t i = selector[0].size()-1; i >= 0; i--) {
125+ for (int i = selector[0].size()-1; i >= 0; i--) {
126 std::string part = selector[0][i];
127- if (part != '>' || part != ' ' || part != '+' || part != '~') {
128- parts.insert(0, part);
129+ if (symbolFound) {
130+ trimmed.insert(trimmed.begin(), part);
131 } else {
132- break;
133+ if (part != ">" || part != " " || part != "+" || part != "~") {
134+ parts.insert(parts.begin(), part);
135+ } else {
136+ symbol = part;
137+ }
138 }
139 }
140-
141-
142+
143+
144+ // Given our selector is: {{"div", ">", "h1"}}
145+ // we should now have
146+ // trimmed = {"div"}
147+ // parts = {"h1"}
148+ // symbol = ">"
149+
150+
151+ // matched is true here because the node matching logic
152+ // returns if matched is false
153 bool matched = true;
154
155 std::vector<std::string> nodeClasses = node->classList.values();
156 std::unordered_map<std::string, std::string> attributes = node->getAttributes();
157-
158-
159 std::vector<std::string> attrs;
160
161 for (auto a : attributes) {
162@@ -444,7 +471,7 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
163 }
164
165 size_t i = 0;
166- for (auto p : pParts) {
167+ for (auto p : parts) {
168 i++;
169 if (i == 0 && p[0] != '#' && p[0] != '.' && p[0] != '[') {
170 matched = node->getTagName() == p;
171@@ -470,7 +497,7 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
172 }
173 }
174 matched = found;
175- }
176+ } // !TODO: Need a case for : , this will run the :has... logic
177
178 if (!matched) {
179 // Return out of the function
180@@ -478,17 +505,26 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
181 }
182 }
183
184+ // if the node did not match it would have returned false so if there isn't
185+ // a symbol then we return true
186+
187+ if (symbol == "") {
188+ return true;
189+ }
190+
191 // The rest of the logic assumes the other selectors do not match until proven otherwise
192 matched = false;
193
194- if (symbol == '\0' || node->parent != nullptr) {
195+ if (node->parent != nullptr) {
196 return false;
197 }
198
199- if (symbol == '>') {
200+ if (symbol == ">") {
201+ // child cominator
202 // We are just testing the parent node here
203- matched = testSelector(node->parent, trimmed);
204- } else if (symbol == '+') {
205+ matched = testSelector(node->parent, {trimmed});
206+ } else if (symbol == "+") {
207+ // next sibling combinator
208 const auto& children = node->parent->children;
209 if (children.size() > 1) {
210 for (size_t i = 0; i < children.size(); i++) {
211@@ -499,13 +535,14 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
212 return false;
213 } else {
214 // In this selector we just need to get the direct sibling and test it
215- matched = testSelector(children[i-1].get(), trimmed);
216+ matched = testSelector(children[i-1].get(), {trimmed});
217 }
218 break;
219 }
220 }
221 }
222- } else if (symbol == '~') {
223+ } else if (symbol == "~") {
224+ // Subsequent-sibling combinator
225 const auto& children = node->parent->children;
226 if (children.size() > 1) {
227 for (size_t i = 0; i < children.size(); i++) {
228@@ -516,18 +553,18 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
229 } else {
230 // We have to test every selector before the current element,
231 // if any match then it matches
232- matched = testSelector(children[i].get(), trimmed);
233+ matched = testSelector(children[i].get(), {trimmed});
234 if (matched) {
235 break;
236 }
237 }
238 }
239 }
240- } else if (symbol == ' ') {
241+ } else if (symbol == " ") {
242 // For the descendant combinator we have to check every parent until we match or nullptr out
243 Node* current = node->parent;
244 while (current != nullptr) {
245- if (testSelector(current, trimmed)) {
246+ if (testSelector(current, {trimmed})) {
247 matched = true;
248 break;
249 }
250@@ -536,4 +573,4 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
251 }
252
253 return matched;
254-}*/
255+}
256diff --git a/tests/css_selector.cc b/tests/css_selector.cc
257index daf6bae..ab00d29 100644
258--- a/tests/css_selector.cc
259+++ b/tests/css_selector.cc
260@@ -1,8 +1,9 @@
261 #include <catch_amalgamated.hpp>
262 #include "../include/grim.h"
263+#include "../include/parser.h"
264 #include <string>
265-#include <unordered_set>
266 #include <fstream>
267+#include <iostream>
268
269 void checkSelectorParts(std::string selector, std::vector<std::vector<std::string>> expected) {
270 INFO("=========================");
271@@ -44,7 +45,7 @@ TEST_CASE( "CSS Selection", "[css]" ) {
272 };
273 }
274 SECTION("parseSelectorParts can parse a simple CSS selector with a psuedo class selector") {
275- checkSelectorParts("div > h1:checked", {{"div", ">", "h1",":checked"}});
276+ checkSelectorParts("div > h1:checked", {{"div", ">", "h1","[checked]"}});
277 BENCHMARK("div > h1:checked") {
278 return parseSelectorParts("div > h1:checked");
279 };
280@@ -69,7 +70,7 @@ TEST_CASE( "CSS Selection", "[css]" ) {
281 };
282 }
283 SECTION("parseSelectorParts can parse multiple simple selectors"){
284- checkSelectorParts("input:required, div#id + h1.c1.c2, input[type='password'].class", {{"input", ":required"},{"div", "#id", "+", "h1", ".c1", ".c2"},{"input", "[type=\"password\"]", ".class"}});
285+ checkSelectorParts("input:required, div#id + h1.c1.c2, input[type='password'].class", {{"input", "[required]"},{"div", "#id", "+", "h1", ".c1", ".c2"},{"input", "[type=\"password\"]", ".class"}});
286 BENCHMARK("input:required, div#id + h1.c1.c2, input[type='password'].class") {
287 return parseSelectorParts("input:required, div#id + h1.c1.c2, input[type='password'].class");
288 };
289@@ -80,4 +81,36 @@ TEST_CASE( "CSS Selection", "[css]" ) {
290 return parseSelectorParts("div h1");
291 };
292 }
293+ SECTION("parseSelectorParts can parse a selector with a decendent combinator") {
294+ checkSelectorParts("div:has(h1):checked", {{"div", ":has(h1)", "[checked]"}});
295+ BENCHMARK("div h1") {
296+ return parseSelectorParts("div h1");
297+ };
298+ }
299+}
300+
301+TEST_CASE("testSelector", "[CSS]") {
302+ std::string html = "<!DOCTYPE html>"
303+ "<html>"
304+ "<head>"
305+ "<title>This is a simple test</title>"
306+ "<link rel=\"stylesheet\" href=\'/style.css\'/>"
307+ "</head>"
308+ "<body>"
309+ "<p id=\"paragraph\" class=\"class1 class2\">This is some text for a simple test</p>"
310+ "</body>"
311+ "</html>";
312+
313+
314+ std::stringstream ss(html);
315+ std::unique_ptr<Node> document = parseStream(ss);
316+
317+ std::cout << document->getTagName() << std::endl;
318+
319+ SECTION("testSelector can match a element given it's tagName") {
320+ auto html = document->children[0].get();
321+ INFO(html->getTagName());
322+ bool match = testSelector(html, parseSelectorParts("nothtml"));
323+ REQUIRE(match);
324+ }
325 }