home readme diff tree note docs

0commit 56074a6bfe4498d092f3a227297c8c20e2bb962c
1Author: Mason Wright <mason@lakefox.net>
2Date:   Sun Jun 22 19:42:58 2025 -0600
3
4    Working on testSelector
5
6diff --git a/build/grim.o b/build/grim.o
7index d30b88a..a655a9c 100644
8Binary files a/build/grim.o and b/build/grim.o differ
9diff --git a/include/grim.h b/include/grim.h
10index 0919017..05f148b 100644
11--- a/include/grim.h
12+++ b/include/grim.h
13@@ -30,9 +30,10 @@ struct State {
14 
15 class ClassList {
16 private:
17-    std::vector<std::string> values;
18+    std::vector<std::string> classes;
19 public:
20     std::string value() const;
21+    std::vector<std::string> values();
22     void add(std::string value);
23     void remove(std::string value);
24 };
25@@ -83,6 +84,8 @@ public:
26     void setAttribute(const std::string& name, const std::string& value);
27     std::string getAttribute(const std::string& name) const;
28 
29+    std::vector<std::string> getAttributeKeys();
30+
31     void print(int indent = 0) const;
32 };
33 
34diff --git a/main b/main
35index 29af90c..f079efc 100755
36Binary files a/main and b/main differ
37diff --git a/src/grim.cc b/src/grim.cc
38index 6d356b9..9950d0c 100755
39--- a/src/grim.cc
40+++ b/src/grim.cc
41@@ -6,25 +6,29 @@
42 #include <cctype>
43 
44 std::string ClassList::value() const {
45-    if (values.empty()) {
46+    if (classes.empty()) {
47         return "";
48     }
49-    std::string collection = values[0];
50-    for (size_t i = 1; i < values.size(); ++i) {
51-        collection += " " + values[i];
52+    std::string collection = classes[0];
53+    for (size_t i = 1; i < classes.size(); ++i) {
54+        collection += " " + classes[i];
55     }
56     return collection;
57 }
58 
59+std::vector<std::string> ClassList::values() {
60+	return classes;
61+}
62+
63 void ClassList::add(std::string value) {
64-    values.push_back(value);
65+    classes.push_back(value);
66 }
67 
68 void ClassList::remove(std::string value) {
69-    auto it_prev = std::find(values.begin(), values.end(), value);
70-    if (it_prev != values.end()) {
71-        *it_prev = values.back();
72-        values.pop_back();
73+    auto it_prev = std::find(classes.begin(), classes.end(), value);
74+    if (it_prev != classes.end()) {
75+        *it_prev = classes.back();
76+        classes.pop_back();
77     }
78 }
79 
80@@ -132,6 +136,15 @@ std::string Node::getAttribute(const std::string& name) const {
81     return "";
82 }
83 
84+std::vector<std::string> Node::getAttributeKeys() {
85+	std::vector<std::string> keys;
86+
87+	for(auto p : Attributes) {
88+		keys.push_back(p.first);
89+	}
90+	return keys;
91+}
92+
93 void Node::print(int indent) const {
94     for (int i = 0; i < indent; ++i) {
95         std::cout << "  ";
96@@ -169,7 +182,7 @@ struct Style {
97 	std::unordered_map<std::string, std::string> properties;
98 	std::string selector;
99 	// Index of when it was added (for cascading)
100-	int index;
101+	size_t index;
102 };
103 
104 // struct BaseParts {
105@@ -184,15 +197,13 @@ class StyleHandler {
106 		// basemap: Maps baseparts to a index of the styles vector pointing to a Style object
107 		// because processing a CSS selector isn't trivial we want to make a short list of 
108 		// the styles that can possibly be a match so we aren't spending a lot of compute 
109-		// on a selector that applys to the wrong element. Todo this we take the right most part
110+		// on a selector that applies to the wrong element. Todo this we take the right most part
111 		// of a selector (that is the part that targets the current element) and we do a few checks
112 		// like is the tagname the same, does it have that class, and do the id's match. If so that 
113-		// is a canidate and we run the full selector on it. That includes checking parents,children, 
114+		// is a candidate and we run the full selector on it. That includes checking parents,children, 
115 		// or what ever else the selector needs to match.
116 		std::unordered_map<std::string, std::vector<size_t>> basemap;
117 		std::vector<Style> styles;
118-		int currentIndex = 0;
119-
120 
121 		std::vector<std::string> parseSelectorParts(std::string selector) {
122 			// we want to find the right most (right of a " " > ~ + ) selector
123@@ -227,7 +238,7 @@ class StyleHandler {
124 					// so we can build the basemap
125 					std::string part;
126 					for (auto w : word) {
127-						// if the current letter is a : that is either a psuedo element
128+						// if the current letter is a : that is either a pseudo element
129 						// or selector. We don't care about that part for this part
130 						if (w != ':') {
131 							// We don't care if the thing we are splitting is a id or class we just 
132@@ -284,18 +295,257 @@ class StyleHandler {
133 		}
134 
135 
136-		// std::vector<std::string> parseNodeParts(Node* node) {
137+		std::vector<std::string> parseNodeParts(Node* node) {
138+			std::vector<std::string> parts;
139+
140+			parts.push_back(node->getTagName());
141+			parts.push_back("#"+node->getId());
142+
143+			// classes and attributes
144+			auto keys = node->getAttributeKeys();
145+
146+			for (auto k : keys) {
147+				if (k != "tagName" || k != "id" || k != "innerText" || k != "class") {
148+					std::string v;
149+					std::string av = node->getAttribute(k);
150+					if (av != "") {
151+							v = "=\""+av+"\"";
152+
153+					}
154+					parts.push_back(k+v);
155+				}
156+			}
157+
158+			auto classes = node->classList.values();
159+
160+			for (auto c : classes) {
161+				parts.push_back("."+c);
162+			}
163+
164+			return parts;
165+		}
166+
167+		std::string cleanSelector(std::string selector) {
168+			std::string cleaned;
169+			for (size_t i = 0; i < selector.length(); i++) {
170+				if (selector[i] == ' ' && i > 0 && i < selector.length()-1) {
171+					char prev = selector[i-1];
172+					char next = selector[i+1];
173+
174+					if (prev != '>' && prev != '+' && prev != '~' && next != '>' && next != '+' && next != '~' && !std::isspace(prev) && !std::isspace(next)) {
175+						cleaned += selector[i];
176+					}
177+				} else if (selector[i] == ' ' && (i == 0 || i == selector.length()-1)) {
178+					continue;
179+				} else {
180+					cleaned += selector[i];
181+				}
182+			}
183+			return cleaned;
184+		}
185 	public:
186 		void add(Style style) {
187+			std::string selector = cleanSelector(style.selector);
188+			style.selector = selector;
189+			size_t index = styles.size();
190+
191+			style.index = index;
192 			styles.push_back(std::move(style));
193-			if (basemap.find(styles.back().selector) != basemap.end()) {
194-				basemap[styles.back().selector].push_back(styles.size()-1);
195-			} else {
196-				std::vector<size_t> bm = {styles.size() - 1};
197-				basemap[styles.back().selector] = bm;
198+
199+			std::vector<std::string> parts = parseSelectorParts(selector);
200+
201+			for (auto p : parts) {
202+				if (basemap.find(p) != basemap.end()) {
203+					basemap[p].push_back(index);
204+				} else {
205+					std::vector<size_t> bm = {index};
206+					basemap[p] = bm;
207+				}
208 			}
209-			// Give the properties a cascade index
210-			styles.back().index = currentIndex++;
211 		}
212+		
213+		std::unordered_map<std::string, std::string> getStyles(Node* node) {
214+			std::vector<std::string> parts = parseNodeParts(node);
215+
216+			std::vector<size_t> canidates;
217+
218+			for (auto p : parts) {
219+				if (auto sty = basemap.find(p); sty != basemap.end()) {
220+					for (auto s : sty->second) {
221+						bool found = false;
222+						// This makes sure we aren't adding the same styles to the candidates list
223+						for (auto c : canidates) {
224+							if (c == s) {
225+								found = true;
226+								break;
227+							}
228+						}
229+						if (!found) {
230+							canidates.push_back(s);
231+						}
232+					}
233+				}
234+			}
235 
236+			// Now we have the list of candidates next we will refined our selection
237+			// we need to pull the selectors from each candidate then run testSelector on it
238+		}
239 };
240+
241+std::vector<std::string> splitSelector(std::string selector, char key) {
242+	size_t nesting = 0;
243+
244+	std::vector<std::string> selectors;
245+	std::string current = "";
246+
247+	for (auto s : selector) {
248+		if (s == '(') {
249+			nesting++;
250+		} else if (s == ')') {
251+			nesting--;
252+		} else if (s == '[') {
253+			nesting++;
254+		} else if (s == ']') {
255+			nesting--;
256+		} else if (s == '{') {
257+			nesting++;
258+		} else if (s == '}') {
259+			nesting--;
260+		} else if (s == key && nesting == 0) {
261+			selectors.push_back(current);
262+			current = "";
263+			continue;
264+		}
265+		current += s;
266+	}
267+
268+	selectors.push_back(current);
269+
270+	// Trim the space
271+	for (size_t i = 0; i < selectors.size(); i++) {
272+		size_t start = 0;
273+		size_t end = selectors[i].length()-1;
274+		for (size_t s = 0; s < selectors[i].length(); s++) {
275+			if (!std::isspace(selectors[i][s])) {
276+				start = s;
277+				break;
278+			}
279+		}
280+		for (size_t e = selectors[i].length()-1; e > start; e--) {
281+			if (!std::isspace(selectors[i][e])) {
282+				end = e;
283+				break;
284+			}
285+		}
286+		selectors[i] = selectors[i].substr(start,end - start +1);
287+	}
288+
289+	return selectors;
290+}
291+
292+void popSelector(std::string selector, std::string& trimmed, std::string& popped, char& symbol) {
293+	for (size_t i = selector.length()-1; i >= 0; i--) {
294+		if (
295+				selector[i] == ' ' ||
296+				selector[i] == '>' ||
297+				selector[i] == '~' ||
298+				selector[i] == '+' 
299+		   ) {
300+			symbol = selector[i];
301+			trimmed = selector.substr(0,i);
302+			break;
303+		} else {
304+			popped = selector[i]+popped;
305+		}
306+	}
307+}
308+
309+bool testSelector(Node* node, std::string selector) {
310+	// By the time we get here, we know the right most selector some what matches
311+	// so now if it is a compound selector we should see if the parent('s) some what match
312+	if (selector == '*') {
313+		return true;
314+	}
315+
316+	std::vector<std::string> selectors = splitSelector(selector, ',');
317+
318+	if (selectors.size() > 1) {
319+		for (auto s : selectors) {
320+			bool match = testSelector(node, s);
321+
322+			if (match) {
323+				return true;
324+			}
325+		}
326+	}
327+
328+	std::string trimmed, popped;
329+	char symbol;
330+	popSelector(selector, trimmed, popped, symbol);
331+
332+
333+	// Check if popped matches this element
334+	if () {
335+	} else {
336+		return false;
337+	}
338+
339+	if (symbol == '' || node->parent != nullptr) {
340+		return false;
341+	}
342+
343+	bool matched = false;
344+	if (symbol == '>') {
345+		// We are just testing the parent node here
346+		matched = testSelector(node->parent, trimmed);
347+	} else if (symbol == '+') {
348+		auto children = node->parent.children;
349+		if (children.size() > 1) {
350+			for (size_t i = 0; i < children.size(); i++) {
351+				Node* child_ptr = children[i].get();
352+
353+				if (child_ptr == node) {
354+					if (i == 0) {
355+						return false;
356+					} else {
357+						// In this selector we just need to get the direct sibling and test it
358+						matched = testSelector(children[i-1].get(), trimmed);
359+					}
360+					break;
361+				}
362+			}
363+		}
364+	} else if (symbol == '~') {
365+		auto children = node->parent.children;
366+		if (children.size() > 1) {
367+			for (size_t i = 0; i < children.size(); i++) {
368+				Node* child_ptr = children[i].get();
369+
370+				if (child_ptr == node) {
371+					break;
372+				} else {
373+					// We have to test every selector before the current element,
374+					// if any match then it matches
375+					matched = testSelector(children[i].get(), trimmed);
376+					if (matched) {
377+						break;
378+					}
379+				}
380+			}
381+		}
382+	} else if (symbol == ' ') {
383+		// For the descendant combinator we have to check every parent until we match or nullptr out
384+		Node* current = node->parent.get();
385+		while (current != nullptr) {
386+			if (testSelector(current, trimmed)) {
387+				matched = true;
388+				break;
389+			}
390+			current = current->parent.get();
391+		}
392+	}
393+
394+	return matched;
395+}
396+
397+
398diff --git a/src/parser.cc b/src/parser.cc
399index 2bfc103..32ca96f 100644
400--- a/src/parser.cc
401+++ b/src/parser.cc
402@@ -272,6 +272,9 @@ std::unique_ptr<Node> parseStream(std::istream& inputStream) {
403 					break;
404 				}
405 			}
406+
407+			// !TODO: Add a vector that stores inner text and when elements start to close out pop them but add all to their innerText
408+			// + Will need something simular for innerHTML
409 			if (hasText) {
410 				auto node = currentNode->createElement("text");
411 				node->setInnerText(token);