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 }