home readme diff tree note docs

0commit 8f2e83556d12aaebe8e8597ea6923804b0eb7a43
1Author: Mason Wright <mason@lakefox.net>
2Date:   Tue Jun 24 20:39:35 2025 -0600
3
4    Added direct node matching to testSelector
5
6diff --git a/include/grim.h b/include/grim.h
7index 05f148b..239dc8f 100644
8--- a/include/grim.h
9+++ b/include/grim.h
10@@ -86,7 +86,7 @@ public:
11 
12     std::vector<std::string> getAttributeKeys();
13 
14-    void print(int indent = 0) const;
15+    std::string print(int indent = 0);
16 };
17 
18 #endif // NODE_H
19diff --git a/src/grim.cc b/src/grim.cc
20index 7f8677d..6600855 100755
21--- a/src/grim.cc
22+++ b/src/grim.cc
23@@ -145,37 +145,40 @@ std::vector<std::string> Node::getAttributeKeys() {
24 	return keys;
25 }
26 
27-void Node::print(int indent) const {
28+std::string Node::print(int indent) {
29+	std::string out = "";
30     for (int i = 0; i < indent; ++i) {
31-        std::cout << "  ";
32+        out += "  ";
33     }
34 
35-    std::cout << "<" << getTagName();
36+    out += "<" + getTagName();
37     for (const auto& attr_pair : getAttributes()) {
38         if (attr_pair.first == "innerText" || attr_pair.first == "tagName") {
39             continue;
40         }
41-        std::cout << " " << attr_pair.first;
42+        out += " " + attr_pair.first;
43         if (!attr_pair.second.empty()) {
44-            std::cout << "=\"" << attr_pair.second << "\"";
45+            out += "=\"" + attr_pair.second + "\"";
46         }
47     }
48-    std::cout << ">";
49+    out += ">";
50 
51     if (!getAttribute("innerText").empty()) {
52-        std::cout << "\n" << getAttribute("innerText");
53+        out += "\n" + getAttribute("innerText");
54     }
55 
56-    std::cout << std::endl;
57+    out += "\n";
58 
59     for (const auto& child : children) {
60-        child->print(indent + 1);
61+        out += child->print(indent + 1)+"\n";
62     }
63 
64     for (int i = 0; i < indent; ++i) {
65-        std::cout << "  ";
66+        out += "  ";
67     }
68-    std::cout << "</" << getTagName() << ">" << std::endl;
69+    out += "</" + getTagName() + ">\n";
70+
71+    return out;
72 }
73 
74 struct Style {
75@@ -192,122 +195,129 @@ struct Style {
76 // 	std::unordered_map<std::string, std::string> attributes;
77 // }
78 
79-class StyleHandler {
80-	private:
81-		// basemap: Maps baseparts to a index of the styles vector pointing to a Style object
82-		// because processing a CSS selector isn't trivial we want to make a short list of 
83-		// the styles that can possibly be a match so we aren't spending a lot of compute 
84-		// on a selector that applies to the wrong element. Todo this we take the right most part
85-		// of a selector (that is the part that targets the current element) and we do a few checks
86-		// like is the tagname the same, does it have that class, and do the id's match. If so that 
87-		// is a candidate and we run the full selector on it. That includes checking parents,children, 
88-		// or what ever else the selector needs to match.
89-		std::unordered_map<std::string, std::vector<size_t>> basemap;
90-		std::vector<Style> styles;
91-
92-		std::vector<std::string> parseSelectorParts(std::string selector) {
93-			// we want to find the right most (right of a " " > ~ + ) selector
94-			// account for * for commas split the selector then for each parse right
95-			
96-			std::vector<std::string> parts;
97-			std::string word;
98+std::vector<std::string> parseSelectorParts(std::string selector) {
99+	// we want to find the right most (right of a " " > ~ + ) selector
100+	// account for * for commas split the selector then for each parse right
101 
102-			for (size_t s = 0; s < selector.length(); s++) {
103-				// Start parsing if we see a comma or the end of the selector
104-				word += selector[s];
105+	std::vector<std::string> parts;
106+	std::string word;
107 
108-				if (selector[s] == ',' || s == selector.length()-1) {
109-					if (selector[s] == ',') {
110-						word.pop_back(); // Remove the trailing comma
111-					}
112-					// Break the tag and add the right most parts to parts
113-					for (int i = word.length()-1; i > -1; i--) {
114-						if (
115-								word[i] == ' ' ||
116-								word[i] == '>' ||
117-								word[i] == '~' ||
118-								word[i] == '+' 
119-								) {
120-							word = word.substr(i+1);
121-							break;
122-						}
123-					}
124+	for (size_t s = 0; s < selector.length(); s++) {
125+		// Start parsing if we see a comma or the end of the selector
126+		word += selector[s];
127 
128-					// Now word contains the right most selection
129-					// we will extract the quick to verify parts from it
130-					// so we can build the basemap
131-					std::string part;
132-					bool inbracket = false;
133-					for (auto w : word) {
134-						// if the current letter is a : that is either a pseudo element
135-						// or selector. We don't care about that part for this part
136-						if (w == ':' && !inbracket) {
137-							break;
138-						}
139-						// We don't care if the thing we are splitting is a id or class we just
140-						// need them split up so we can use them as map keys
141-
142-						if (w == '#' && !inbracket) {
143-							if (part != "") {
144-								parts.push_back(part);
145-								part = "";
146-							}
147-
148-						} else if (w == '.' && !inbracket) {
149-							if (part != "") {
150-								parts.push_back(part);
151-								part = "";
152-							}
153-						} else if (w == '[' || w == ']') {
154-							if (part != "" && inbracket) {
155-								parts.push_back('['+part+']');
156-								part = "";
157-							} else if (!inbracket) {
158-								parts.push_back(part);
159-								part = "";
160-							}
161+		if (selector[s] == ',' || s == selector.length()-1) {
162+			if (selector[s] == ',') {
163+				word.pop_back(); // Remove the trailing comma
164+			}
165+			// Break the tag and add the right most parts to parts
166+			for (int i = word.length()-1; i > -1; i--) {
167+				if (
168+						word[i] == ' ' ||
169+						word[i] == '>' ||
170+						word[i] == '~' ||
171+						word[i] == '+' 
172+				   ) {
173+					word = word.substr(i+1);
174+					break;
175+				}
176+			}
177 
178-							if (w=='[') {
179-								inbracket = true;
180-							} else if (w == ']'){
181-								inbracket = false;
182-							}
183-							continue;
184-						}
185+			// Now word contains the right most selection
186+			// we will extract the quick to verify parts from it
187+			// so we can build the basemap
188+			std::string part;
189+			bool inbracket = false;
190+			for (auto w : word) {
191+//				Need to add :checked etc parsing
192+				if (w == ':' && !inbracket) {
193+					break;
194+				}
195+				// We don't care if the thing we are splitting is a id or class we just
196+				// need them split up so we can use them as map keys
197 
198-						part += w;
199+				if (w == '#' && !inbracket) {
200+					if (part != "") {
201+						parts.push_back(part);
202+						part = "";
203 					}
204 
205+				} else if (w == '.' && !inbracket) {
206 					if (part != "") {
207 						parts.push_back(part);
208+						part = "";
209 					}
210+				} else if (w == '[' || w == ']') {
211+
212+					// We need to check if its in brackets or not because the [ will
213+					// close the last .class, if we don't check then it will be in brackets
214+					if (part != "" && inbracket) {
215+						parts.push_back('['+part+']');
216+						part = "";
217+					} else if (!inbracket) {
218+						parts.push_back(part);
219+						part = "";
220+					}
221+
222+					if (w=='[') {
223+						inbracket = true;
224+					} else if (w == ']'){
225+						inbracket = false;
226+					}
227+					continue;
228+				}
229+
230+				// We need to keep a consistant quote mark to make matching reliable
231+				if (inbracket && w == '\'') {
232+					w = '"';
233 				}
234+
235+				part += w;
236 			}
237 
238-			std::vector<std::string> deduplicated;
239+			if (part != "") {
240+				parts.push_back(part);
241+			}
242+		}
243+	}
244 
245-			for (size_t p1 = 0; p1 < parts.size(); p1++) {
246-				bool matches = false;
247+	std::vector<std::string> deduplicated;
248 
249-				if (parts[p1] == "") {
250-					continue;
251-				}
252+	for (size_t p1 = 0; p1 < parts.size(); p1++) {
253+		bool matches = false;
254 
255-				for (size_t p2 = p1+1; p2 < parts.size(); p2++) {
256-					if (parts[p1] == parts[p2]) {
257-						matches = true;
258-					}
259-				}
260+		if (parts[p1] == "") {
261+			continue;
262+		}
263 
264-				if (!matches) {
265-					deduplicated.push_back(parts[p1]);
266-				}
267+		for (size_t p2 = p1+1; p2 < parts.size(); p2++) {
268+			if (parts[p1] == parts[p2]) {
269+				matches = true;
270 			}
271+		}
272 
273-			return deduplicated;
274+		if (!matches) {
275+			deduplicated.push_back(parts[p1]);
276 		}
277+	}
278 
279+	return deduplicated;
280+}
281 
282+class StyleHandler {
283+	private:
284+		// basemap: Maps baseparts to a index of the styles vector pointing to a Style object
285+		// because processing a CSS selector isn't trivial we want to make a short list of 
286+		// the styles that can possibly be a match so we aren't spending a lot of compute 
287+		// on a selector that applies to the wrong element. Todo this we take the right most part
288+		// of a selector (that is the part that targets the current element) and we do a few checks
289+		// like is the tagname the same, does it have that class, and do the id's match. If so that 
290+		// is a candidate and we run the full selector on it. That includes checking parents,children, 
291+		// or what ever else the selector needs to match.
292+		std::unordered_map<std::string, std::vector<size_t>> basemap;
293+		std::vector<Style> styles;
294+
295+		
296 		std::vector<std::string> parseNodeParts(Node* node) {
297 			std::vector<std::string> parts;
298 
299@@ -473,10 +483,11 @@ void popSelector(std::string selector, std::string& trimmed, std::string& popped
300 	}
301 }
302 
303+
304 bool testSelector(Node* node, std::string selector) {
305 	// By the time we get here, we know the right most selector some what matches
306 	// so now if it is a compound selector we should see if the parent('s) some what match
307-	if (selector == '*') {
308+	if (selector == "*") {
309 		return true;
310 	}
311 
312@@ -498,21 +509,72 @@ bool testSelector(Node* node, std::string selector) {
313 
314 
315 	// Check if popped matches this element
316-	if () {
317-	} else {
318-		return false;
319+	
320+	std::vector<std::string> pParts = parseSelectorParts(popped);
321+
322+	bool matched = true;
323+
324+	std::vector<std::string> nodeClasses = node->classList.values();
325+	std::unordered_map<std::string, std::string> attributes = node->getAttributes();
326+
327+
328+	std::vector<std::string> attrs;
329+
330+	for (auto a : attributes) {
331+		if (a.second != "") {
332+			attrs.push_back("["+a.first+"=\""+a.second+"\"]");
333+		} else {
334+
335+			attrs.push_back("["+a.first+"]");
336+		}
337 	}
338 
339-	if (symbol == '' || node->parent != nullptr) {
340-		return false;
341+	for (size_t i = 0; i < pParts.size(); i++) {
342+		std::string p = pParts[i];
343+		if (i == 0 && p[0] != '#' && p[0] != '.' &&  p[0] != '[') {
344+			matched = node->getTagName() == p;
345+		} else if (p[0] == '#') {
346+			matched = node->getId() == p;
347+		} else if (p[0] == '.') {
348+			// Remove the period
349+			std::string clipped = p.substr(1);
350+			bool found = false;
351+			for (auto c : nodeClasses) {
352+				if (clipped == c) {
353+					found = true;
354+					break;
355+				}
356+			}
357+			matched = found;
358+		} else if (p[0] == '[') {
359+			bool found = false;
360+			for (auto a : attrs) {
361+				if (p == a) {
362+					found = true;
363+					break;
364+				}
365+			}
366+			matched = found;
367+		}
368+
369+		if (!matched) {
370+			// Return out of the function
371+			return false;
372+		}
373 	}
374 
375-	bool matched = false;
376+	// The rest of the logic assumes the other selectors do not match until proven otherwise
377+	matched = false;
378+
379+	if (symbol == '\0' || node->parent != nullptr) {
380+		return false;
381+	}
382+	
383 	if (symbol == '>') {
384 		// We are just testing the parent node here
385 		matched = testSelector(node->parent, trimmed);
386 	} else if (symbol == '+') {
387-		auto children = node->parent.children;
388+		const auto& children = node->parent->children;
389 		if (children.size() > 1) {
390 			for (size_t i = 0; i < children.size(); i++) {
391 				Node* child_ptr = children[i].get();
392@@ -529,7 +591,7 @@ bool testSelector(Node* node, std::string selector) {
393 			}
394 		}
395 	} else if (symbol == '~') {
396-		auto children = node->parent.children;
397+		const auto& children = node->parent->children;
398 		if (children.size() > 1) {
399 			for (size_t i = 0; i < children.size(); i++) {
400 				Node* child_ptr = children[i].get();
401@@ -548,13 +610,13 @@ bool testSelector(Node* node, std::string selector) {
402 		}
403 	} else if (symbol == ' ') {
404 		// For the descendant combinator we have to check every parent until we match or nullptr out
405-		Node* current = node->parent.get();
406+		Node* current = node->parent;
407 		while (current != nullptr) {
408 			if (testSelector(current, trimmed)) {
409 				matched = true;
410 				break;
411 			}
412-			current = current->parent.get();
413+			current = current->parent;
414 		}
415 	}
416