home readme diff tree note docs

0commit 5e4c38ff3c212cdd9881427ef3f8c2706539a190
1Author: Mason Wright <mason@lakefox.net>
2Date:   Sun Jun 29 13:33:00 2025 -0600
3
4    Reworked parseSelectorParts and tests
5
6diff --git a/Makefile b/Makefile
7index 714bc8e..7d1531f 100644
8--- a/Makefile
9+++ b/Makefile
10@@ -2,7 +2,7 @@
11 CCFLAGS = -Wall -Wextra -std=c++23
12 CC = clang++
13 
14-.PHONY: all clean run docs tests
15+.PHONY: all clean run docs run_tests
16 
17 all: main
18 
19@@ -18,11 +18,24 @@ build/grim.o: src/grim.cc include/grim.h | build
20 build/parser.o: src/parser.cc include/parser.h | build
21 	${CC} ${CCFLAGS} -Iinclude/ -c src/parser.cc -o build/parser.o
22 
23-tests: build/grim.o build/parser.o build/catch.o
24-	clear && ${CC} ${CCFLAGS} -Itests/ -Iinclude/ ./tests/main.cc ./build/grim.o ./build/parser.o ./build/catch.o -o ./tests/main && ./tests/main
25 
26-build/catch.o: ./tests/catch_amalgamated.cpp ./tests/catch_amalgamated.hpp | build
27-	${CC} ${CCFLAGS} -Itests/ -c ./tests/catch_amalgamated.cpp -o build/catch.o
28+
29+# --- Tests ---
30+
31+run_tests: build/html_parser_test
32+	./build/html_parser_test
33+
34+build/css_selector_test: tests/css_selector.cc build/grim.o build/catch.o | build
35+	${CC} ${CCFLAGS} -Iinclude/ ./tests/css_selector.cc ./build/grim.o ./build/catch.o -o ./build/css_selector_test 
36+
37+build/html_parser_test: tests/html_parser.cc build/grim.o build/parser.o build/catch.o | build
38+	${CC} ${CCFLAGS} -Iinclude/ ./tests/html_parser.cc ./build/grim.o ./build/parser.o ./build/catch.o -o ./build/html_parser_test
39+
40+build/catch.o: ./include/catch_amalgamated.cpp ./include/catch_amalgamated.hpp | build
41+	${CC} ${CCFLAGS} -Iinclude/ -c ./include/catch_amalgamated.cpp -o build/catch.o
42+
43+
44+# --- Tools ---
45 
46 clean:
47 	rm -f main build/*.o
48diff --git a/tests/catch_amalgamated.cpp b/include/catch_amalgamated.cpp
49similarity index 100%
50rename from tests/catch_amalgamated.cpp
51rename to include/catch_amalgamated.cpp
52diff --git a/tests/catch_amalgamated.hpp b/include/catch_amalgamated.hpp
53similarity index 100%
54rename from tests/catch_amalgamated.hpp
55rename to include/catch_amalgamated.hpp
56diff --git a/include/grim.h b/include/grim.h
57index 239dc8f..d0fe315 100644
58--- a/include/grim.h
59+++ b/include/grim.h
60@@ -5,6 +5,7 @@
61 #include <string>
62 #include <vector>
63 #include <unordered_map>
64+#include <unordered_set>
65 #include <memory>
66 
67 struct Styles {
68@@ -89,4 +90,6 @@ public:
69     std::string print(int indent = 0);
70 };
71 
72+std::vector<std::vector<std::string>> parseSelectorParts(std::string);
73+
74 #endif // NODE_H
75diff --git a/src/grim.cc b/src/grim.cc
76index 6600855..25bad64 100755
77--- a/src/grim.cc
78+++ b/src/grim.cc
79@@ -181,127 +181,118 @@ std::string Node::print(int indent) {
80     return out;
81 }
82 
83-struct Style {
84-	std::unordered_map<std::string, std::string> properties;
85-	std::string selector;
86-	// Index of when it was added (for cascading)
87-	size_t index;
88-};
89-
90-// struct BaseParts {
91-// 	std::string tagName;
92-// 	std::string id;
93-// 	std::vector<std::string> classes;
94-// 	std::unordered_map<std::string, std::string> attributes;
95-// }
96 
97-std::vector<std::string> parseSelectorParts(std::string selector) {
98-	// we want to find the right most (right of a " " > ~ + ) selector
99-	// account for * for commas split the selector then for each parse right
100+std::string trimSpace(std::string str) {
101+    int start = 0;
102+    int end = str.length() - 1;
103 
104-	std::vector<std::string> parts;
105-	std::string word;
106+    // Handle empty string case
107+    if (str.empty()) {
108+        return "";
109+    }
110 
111-	for (size_t s = 0; s < selector.length(); s++) {
112-		// Start parsing if we see a comma or the end of the selector
113-		word += selector[s];
114+    // Find the first non-space character
115+    for (/* start is already 0 */; start <= end; ++start) {
116+        if (!std::isspace(str[start])) {
117+            break;
118+        }
119+    }
120 
121-		if (selector[s] == ',' || s == selector.length()-1) {
122-			if (selector[s] == ',') {
123-				word.pop_back(); // Remove the trailing comma
124-			}
125-			// Break the tag and add the right most parts to parts
126-			for (int i = word.length()-1; i > -1; i--) {
127-				if (
128-						word[i] == ' ' ||
129-						word[i] == '>' ||
130-						word[i] == '~' ||
131-						word[i] == '+' 
132-				   ) {
133-					word = word.substr(i+1);
134-					break;
135-				}
136-			}
137+    // If the loop finished, it means the string was all spaces or empty
138+    if (start > end) {
139+        return ""; // Or a string of spaces, depending on desired behavior for "   "
140+    }
141 
142-			// Now word contains the right most selection
143-			// we will extract the quick to verify parts from it
144-			// so we can build the basemap
145-			std::string part;
146-			bool inbracket = false;
147-			for (auto w : word) {
148-//				Need to add :checked etc parsing
149-				if (w == ':' && !inbracket) {
150-					break;
151-				}
152-				// We don't care if the thing we are splitting is a id or class we just
153-				// need them split up so we can use them as map keys
154+    // Find the last non-space character
155+    for (/* end is already str.length() - 1 */; end >= start; --end) {
156+        if (!std::isspace(str[end])) {
157+            break;
158+        }
159+    }
160 
161-				if (w == '#' && !inbracket) {
162-					if (part != "") {
163-						parts.push_back(part);
164-						part = "";
165-					}
166+    // Calculate the length of the substring
167+    // The length is (end_index - start_index) + 1
168+    return str.substr(start, end - start + 1);
169+}
170 
171-				} else if (w == '.' && !inbracket) {
172-					if (part != "") {
173-						parts.push_back(part);
174-						part = "";
175-					}
176-				} else if (w == '[' || w == ']') {
177-
178-					// We need to check if its in brackets or not because the [ will
179-					// close the last .class, if we don't check then it will be in brackets
180-					if (part != "" && inbracket) {
181-						parts.push_back('['+part+']');
182-						part = "";
183-					} else if (!inbracket) {
184-						parts.push_back(part);
185-						part = "";
186-					}
187+struct Style {
188+	std::unordered_map<std::string, std::string> properties;
189+	std::vector<std::vector<std::string>> selector;
190+	// Index of when it was added (for cascading)
191+	size_t index;
192+};
193 
194-					if (w=='[') {
195-						inbracket = true;
196-					} else if (w == ']'){
197-						inbracket = false;
198-					}
199-					continue;
200-				}
201+// parseSelectorParts deconstructs a selector into its indiviual parts
202+// so they can be store and used in things like finding styles for basemap
203+// and comparing a node to a selector in testSelector. This is a higher level function
204+// that should be ran once perselector and the results saved somewhere
205+std::vector<std::vector<std::string>> parseSelectorParts(std::string selector) {
206+	// need to account for selectors with parenthesis like: h1:(+ input:required)
207+	// need to convert all single quotes to double quotes
208+	// need to account for commas, will return
209+	// need to colapse all spaces to a single space
210+	// need to trim spaces
211+
212+	std::vector<std::vector<std::string>> parts;
213+	std::vector<std::string> buffer;
214+	std::string current;
215+	size_t nesting = 0;
216 
217-				// We need to keep a consistant quote mark to make matching reliable
218-				if (inbracket && w == '\'') {
219-					w = '"';
220-				}
221+	size_t sl = selector.length();
222 
223-				part += w;
224-			}
225+	for (size_t e = 0; e < sl; e++) {
226+		char s = selector[e];
227+		 
228+		if (s == '\'') {
229+			// convert single quotes to double quotes
230+			s = '\"';
231+		}
232 
233-			if (part != "") {
234-				parts.push_back(part);
235+		if (s == ' ' && nesting == 0) {
236+			if (e > 0 && selector[e-1] != ':' && selector[e-1] != ',' && selector[e-1] != '#' && selector[e-1] != '.') {
237+				continue;
238+			} else 
239+			if (e < sl-1 && selector[e+1] != ':' && selector[e+1] != ',' && selector[e+1] != '#' && selector[e+1] != '.') {
240+				continue;
241 			}
242 		}
243-	}
244-
245-	std::vector<std::string> deduplicated;
246 
247-	for (size_t p1 = 0; p1 < parts.size(); p1++) {
248-		bool matches = false;
249-
250-		if (parts[p1] == "") {
251-			continue;
252+		if (nesting == 0 && !current.empty()) {
253+			// !ISSUE: Missing space
254+			if (s == ':' || s == '[' || s == ',' || s == '#' || s == '.' || s == ' ') {
255+				buffer.push_back(trimSpace(current));
256+				current = s;
257+			} else 
258+			if (s == '>' || s == '+' || s == '~') {
259+				buffer.push_back(trimSpace(current));
260+				buffer.push_back("");
261+				buffer.back() += s;
262+				current = "";
263+			} else {
264+				current += s;
265+			}
266+		} else {
267+			current += s;
268 		}
269 
270-		for (size_t p2 = p1+1; p2 < parts.size(); p2++) {
271-			if (parts[p1] == parts[p2]) {
272-				matches = true;
273+		if ((s == ',' && nesting == 0) || e == sl-1) {
274+			//std::cout << selector.substr(start, e-start+1) << std::endl;
275+			if (!current.empty() && current != ",") {
276+				buffer.push_back(trimSpace(current));
277 			}
278+			parts.push_back(buffer);
279+			buffer = {};
280+			current = "";
281 		}
282-
283-		if (!matches) {
284-			deduplicated.push_back(parts[p1]);
285+		if (s == '(' || s == '[' || s == '{') {
286+			nesting++;
287+		} else if (s == ')' || s == ']' || s == '}') {
288+			nesting--;
289 		}
290-	}
291 
292-	return deduplicated;
293+	}	
294+
295+	return parts;
296 }
297 
298 class StyleHandler {
299@@ -348,36 +339,17 @@ class StyleHandler {
300 			return parts;
301 		}
302 
303-		std::string cleanSelector(std::string selector) {
304-			std::string cleaned;
305-			for (size_t i = 0; i < selector.length(); i++) {
306-				if (selector[i] == ' ' && i > 0 && i < selector.length()-1) {
307-					char prev = selector[i-1];
308-					char next = selector[i+1];
309-
310-					if (prev != '>' && prev != '+' && prev != '~' && next != '>' && next != '+' && next != '~' && !std::isspace(prev) && !std::isspace(next)) {
311-						cleaned += selector[i];
312-					}
313-				} else if (selector[i] == ' ' && (i == 0 || i == selector.length()-1)) {
314-					continue;
315-				} else {
316-					cleaned += selector[i];
317-				}
318-			}
319-			return cleaned;
320-		}
321 	public:
322-		void add(Style style) {
323-			std::string selector = cleanSelector(style.selector);
324-			style.selector = selector;
325+		void add(std::string selector, std::unordered_map<std::string, std::string> properties) {
326+			// Type is 2d vector of selector parts
327+			auto parts = parseSelectorParts(selector);
328 			size_t index = styles.size();
329+			Style style = {properties,parts,index};
330 
331-			style.index = index;
332 			styles.push_back(std::move(style));
333-
334-			std::vector<std::string> parts = parseSelectorParts(selector);
335-
336-			for (auto p : parts) {
337+// !ISSUE: Re do the basemap mapping in the add function
338+// + will need to go through each 1d then once in start from right and go left until >+~" " or eol 
339+/*			for (auto p : parts) {
340 				if (basemap.find(p) != basemap.end()) {
341 					basemap[p].push_back(index);
342 				} else {
343@@ -385,6 +357,7 @@ class StyleHandler {
344 					basemap[p] = bm;
345 				}
346 			}
347+*/
348 		}
349 		
350 		std::unordered_map<std::string, std::string> getStyles(Node* node) {
351@@ -415,56 +388,7 @@ class StyleHandler {
352 		}
353 };
354 
355-std::vector<std::string> splitSelector(std::string selector, char key) {
356-	size_t nesting = 0;
357-
358-	std::vector<std::string> selectors;
359-	std::string current = "";
360-
361-	for (auto s : selector) {
362-		if (s == '(') {
363-			nesting++;
364-		} else if (s == ')') {
365-			nesting--;
366-		} else if (s == '[') {
367-			nesting++;
368-		} else if (s == ']') {
369-			nesting--;
370-		} else if (s == '{') {
371-			nesting++;
372-		} else if (s == '}') {
373-			nesting--;
374-		} else if (s == key && nesting == 0) {
375-			selectors.push_back(current);
376-			current = "";
377-			continue;
378-		}
379-		current += s;
380-	}
381-
382-	selectors.push_back(current);
383 
384-	// Trim the space
385-	for (size_t i = 0; i < selectors.size(); i++) {
386-		size_t start = 0;
387-		size_t end = selectors[i].length()-1;
388-		for (size_t s = 0; s < selectors[i].length(); s++) {
389-			if (!std::isspace(selectors[i][s])) {
390-				start = s;
391-				break;
392-			}
393-		}
394-		for (size_t e = selectors[i].length()-1; e > start; e--) {
395-			if (!std::isspace(selectors[i][e])) {
396-				end = e;
397-				break;
398-			}
399-		}
400-		selectors[i] = selectors[i].substr(start,end - start +1);
401-	}
402-
403-	return selectors;
404-}
405 
406 void popSelector(std::string selector, std::string& trimmed, std::string& popped, char& symbol) {
407 	for (size_t i = selector.length()-1; i >= 0; i--) {
408@@ -483,7 +407,7 @@ void popSelector(std::string selector, std::string& trimmed, std::string& popped
409 	}
410 }
411 
412-
413+/*
414 bool testSelector(Node* node, std::string selector) {
415 	// By the time we get here, we know the right most selector some what matches
416 	// so now if it is a compound selector we should see if the parent('s) some what match
417@@ -491,17 +415,6 @@ bool testSelector(Node* node, std::string selector) {
418 		return true;
419 	}
420 
421-	std::vector<std::string> selectors = splitSelector(selector, ',');
422-
423-	if (selectors.size() > 1) {
424-		for (auto s : selectors) {
425-			bool match = testSelector(node, s);
426-
427-			if (match) {
428-				return true;
429-			}
430-		}
431-	}
432 
433 	std::string trimmed, popped;
434 	char symbol;
435@@ -510,7 +423,7 @@ bool testSelector(Node* node, std::string selector) {
436 
437 	// Check if popped matches this element
438 	
439-	std::vector<std::string> pParts = parseSelectorParts(popped);
440+	std::vector<std::vector<std::string>> pParts = parseSelectorParts(popped);
441 
442 	bool matched = true;
443 
444@@ -528,9 +441,10 @@ bool testSelector(Node* node, std::string selector) {
445 			attrs.push_back("["+a.first+"]");
446 		}
447 	}
448-
449-	for (size_t i = 0; i < pParts.size(); i++) {
450-		std::string p = pParts[i];
451+	
452+	size_t i = 0;
453+	for (auto p : pParts) {
454+		i++;
455 		if (i == 0 && p[0] != '#' && p[0] != '.' &&  p[0] != '[') {
456 			matched = node->getTagName() == p;
457 		} else if (p[0] == '#') {
458@@ -623,4 +537,4 @@ bool testSelector(Node* node, std::string selector) {
459 	return matched;
460 }
461 
462-
463+*/
464diff --git a/src/grim.cc.bak b/src/grim.cc.bak
465new file mode 100755
466index 0000000..36ec981
467--- /dev/null
468+++ b/src/grim.cc.bak
469@@ -0,0 +1,602 @@
470+#include "grim.h"
471+#include <iostream>
472+#include <sstream>
473+#include <algorithm>
474+#include <stdexcept>
475+#include <cctype>
476+
477+std::string ClassList::value() const {
478+    if (classes.empty()) {
479+        return "";
480+    }
481+    std::string collection = classes[0];
482+    for (size_t i = 1; i < classes.size(); ++i) {
483+        collection += " " + classes[i];
484+    }
485+    return collection;
486+}
487+
488+std::vector<std::string> ClassList::values() {
489+	return classes;
490+}
491+
492+void ClassList::add(std::string value) {
493+    classes.push_back(value);
494+}
495+
496+void ClassList::remove(std::string value) {
497+    auto it_prev = std::find(classes.begin(), classes.end(), value);
498+    if (it_prev != classes.end()) {
499+        *it_prev = classes.back();
500+        classes.pop_back();
501+    }
502+}
503+
504+// Constructor
505+Node::Node() : parent(nullptr) {}
506+
507+std::string Node::getTagName() const { return TagName; }
508+void Node::setTagName(const std::string& name) { TagName = name; }
509+
510+const std::unordered_map<std::string, std::string>& Node::getAttributes() const {
511+    return Attributes;
512+}
513+
514+// Implement the getter/setter methods declared using the macro
515+#define IMPLEMENT_ATTRIBUTE_ACCESSORS(_Type, _FuncNameSuffix, _AttrKeyString) \
516+    _Type Node::get##_FuncNameSuffix() const { \
517+        return getAttribute<_Type>(_AttrKeyString); \
518+    } \
519+    void Node::set##_FuncNameSuffix(_Type value) { \
520+        setAttribute(_AttrKeyString, value); \
521+    }
522+
523+IMPLEMENT_ATTRIBUTE_ACCESSORS(std::string, Id, "id")
524+IMPLEMENT_ATTRIBUTE_ACCESSORS(std::string, InnerText, "innerText")
525+IMPLEMENT_ATTRIBUTE_ACCESSORS(bool, ContentEditable, "contenteditable")
526+IMPLEMENT_ATTRIBUTE_ACCESSORS(std::string, Href, "href")
527+IMPLEMENT_ATTRIBUTE_ACCESSORS(std::string, Src, "src")
528+IMPLEMENT_ATTRIBUTE_ACCESSORS(std::string, Title, "title")
529+IMPLEMENT_ATTRIBUTE_ACCESSORS(std::string, Value, "value")
530+IMPLEMENT_ATTRIBUTE_ACCESSORS(int, TabIndex, "tabindex")
531+IMPLEMENT_ATTRIBUTE_ACCESSORS(bool, Disabled, "disabled")
532+IMPLEMENT_ATTRIBUTE_ACCESSORS(bool, Required, "required")
533+IMPLEMENT_ATTRIBUTE_ACCESSORS(bool, Checked, "checked")
534+
535+Node* Node::createElement(std::string name) {
536+    std::unique_ptr<Node> newNode = std::make_unique<Node>();
537+    newNode->setTagName(name);
538+    newNode->parent = this;
539+    children.push_back(std::move(newNode));
540+    return children.back().get();
541+}
542+
543+template<typename T>
544+void Node::setAttribute(const std::string& name, const T& value) {
545+    if constexpr (std::is_same_v<T, bool>) {
546+        if (value) {
547+            Attributes[name] = "";
548+        } else {
549+            Attributes.erase(name);
550+        }
551+    } else if constexpr (std::is_arithmetic_v<T>) {
552+        Attributes[name] = std::to_string(value);
553+    } else {
554+        static_assert(std::is_convertible_v<T, std::string>,
555+            "setAttribute: Type cannot be converted to std::string automatically.");
556+        Attributes[name] = static_cast<std::string>(value);
557+    }
558+}
559+
560+void Node::setAttribute(const std::string& name, const std::string& value) {
561+    Attributes[name] = value;
562+}
563+
564+template<typename T>
565+T Node::getAttribute(const std::string& name) const {
566+    auto it = Attributes.find(name);
567+    if (it != Attributes.end()) {
568+        const std::string& s = it->second;
569+
570+        if constexpr (std::is_same_v<T, int>) {
571+            try {
572+                return std::stoi(s);
573+            } catch (const std::invalid_argument& e) {
574+                return 0;
575+            } catch (const std::out_of_range& e) {
576+                return 0;
577+            }
578+        } else if constexpr (std::is_same_v<T, bool>) {
579+            std::string lower_s = s;
580+            std::transform(lower_s.begin(), lower_s.end(), lower_s.begin(),
581+                [](unsigned char c){ return std::tolower(c); });
582+            return (!lower_s.empty() && lower_s != "false");
583+        } else if constexpr (std::is_same_v<T, double>) {
584+            try {
585+                return std::stod(s);
586+            } catch (const std::invalid_argument& e) {
587+                return 0.0;
588+            } catch (const std::out_of_range& e) {
589+                return 0.0;
590+            }
591+        } else {
592+            static_assert(std::is_convertible_v<std::string, T>,
593+                "getAttribute: Type conversion from std::string not implemented for this type.");
594+            return static_cast<T>(s);
595+        }
596+    }
597+    return T();
598+}
599+
600+std::string Node::getAttribute(const std::string& name) const {
601+    auto it = Attributes.find(name);
602+    if (it != Attributes.end()) {
603+        return it->second;
604+    }
605+    return "";
606+}
607+
608+std::vector<std::string> Node::getAttributeKeys() {
609+	std::vector<std::string> keys;
610+
611+	for(auto p : Attributes) {
612+		keys.push_back(p.first);
613+	}
614+	return keys;
615+}
616+
617+std::string Node::print(int indent) {
618+	std::string out = "";
619+    for (int i = 0; i < indent; ++i) {
620+        out += "  ";
621+    }
622+
623+    out += "<" + getTagName();
624+    for (const auto& attr_pair : getAttributes()) {
625+        if (attr_pair.first == "innerText" || attr_pair.first == "tagName") {
626+            continue;
627+        }
628+        out += " " + attr_pair.first;
629+        if (!attr_pair.second.empty()) {
630+            out += "=\"" + attr_pair.second + "\"";
631+        }
632+    }
633+    out += ">";
634+
635+    if (!getAttribute("innerText").empty()) {
636+        out += "\n" + getAttribute("innerText");
637+    }
638+
639+    out += "\n";
640+
641+    for (const auto& child : children) {
642+        out += child->print(indent + 1)+"\n";
643+    }
644+
645+    for (int i = 0; i < indent; ++i) {
646+        out += "  ";
647+    }
648+    out += "</" + getTagName() + ">\n";
649+
650+    return out;
651+}
652+
653+struct Style {
654+	std::unordered_map<std::string, std::string> properties;
655+	std::string selector;
656+	// Index of when it was added (for cascading)
657+	size_t index;
658+};
659+
660+// splitSelector takes a CSS selector and splits it by a key
661+// it ingores any key that is inside (), [], or {}
662+// Used in testSelector to take a compound selector and match each one to the
663+// passed node until a match is found.
664+std::vector<std::string> splitSelector(std::string selector, char key) {
665+	size_t nesting = 0;
666+
667+	std::vector<std::string> selectors;
668+	std::string current = "";
669+
670+	for (auto s : selector) {
671+		if (s == '(') {
672+			nesting++;
673+		} else if (s == ')') {
674+			nesting--;
675+		} else if (s == '[') {
676+			nesting++;
677+		} else if (s == ']') {
678+			nesting--;
679+		} else if (s == '{') {
680+			nesting++;
681+		} else if (s == '}') {
682+			nesting--;
683+		} else if (s == key && nesting == 0) {
684+			selectors.push_back(current);
685+			current = "";
686+			continue;
687+		}
688+		current += s;
689+	}
690+
691+	selectors.push_back(current);
692+
693+	// Trim the space
694+	for (size_t i = 0; i < selectors.size(); i++) {
695+		size_t start = 0;
696+		size_t end = selectors[i].length()-1;
697+		for (size_t s = 0; s < selectors[i].length(); s++) {
698+			if (!std::isspace(selectors[i][s])) {
699+				start = s;
700+				break;
701+			}
702+		}
703+		for (size_t e = selectors[i].length()-1; e > start; e--) {
704+			if (!std::isspace(selectors[i][e])) {
705+				end = e;
706+				break;
707+			}
708+		}
709+		selectors[i] = selectors[i].substr(start,end - start +1);
710+	}
711+
712+	return selectors;
713+}
714+
715+// This function is used to create a "short list" of full selectors 
716+// that may apply to the element we are targeting.
717+// When we add a selector to the StyleHandler we use this function to
718+// create the basemap keys for the style we are adding.
719+// Additionally we will use this in the testSelector function to test each part of an 
720+// of a selector to an element
721+// Example: given body > h1:required
722+// We use parseSelectorParts to get: [h1, :required]
723+// We test the first element given and it is a required h1
724+// Next in the testSelector function we see the selector has a >
725+// so we pass the parent and the second part of the selector:
726+// body
727+// Then the process repeats
728+std::vector<std::string> parseSelectorParts(std::string selector) {
729+	// we want to find the right most (right of a " " > ~ + ) selector
730+	// need to account for selectors with parenthesis like: h1:(+ input:required)
731+	// need to convert all single quotes to double quotes
732+	// need to account for commas, will return
733+	// need to claps all spaces to a single space
734+	// need to trim spaces
735+
736+	std::vector<std::string> parts;
737+	size_t nesting = 0;
738+
739+	std::string current = "";
740+
741+	for (auto s : selector) {
742+		if (s == '(') {
743+			nesting++;
744+		} else if (s == ')') {
745+			nesting--;
746+		} else if (s == '[') {
747+			nesting++;
748+		} else if (s == ']') {
749+			nesting--;
750+		} else if (s == '{') {
751+			nesting++;
752+		} else if (s == '}') {
753+			nesting--;
754+		} else if (s == ',' && nesting == 0) {
755+			// Same logic as splitSelector but we want to inline it here because we 
756+			// are already returning a vector so why make two?
757+			//
758+			// Here is the selector part parsing logic, we go backwards until we hit a combined
759+			std::string part;
760+			bool prevWasSpace = false;
761+			for (size_t i = current.length()-1; i >= 0; i--) {
762+				char c = current[i];
763+				if (c == '(') {
764+					nesting++;
765+					continue;
766+				} else if (c == ')') {
767+					nesting--;
768+					continue;
769+				}
770+
771+				// We will still claps and convert while nested
772+				if (c == ' ') {
773+					// Claps spaces (will not trim)
774+					if (!prevWasSpace) {
775+						part = c + part;
776+						prevWasSpace = true;
777+					}
778+					continue;
779+				} else {
780+					prevWasSpace = false;
781+
782+					if (c == '\'') {
783+						// convert single quotes to double quotes
784+						part = '\"' + part;
785+					}
786+					continue;
787+				}
788+				if (nesting == 0) {
789+					
790+				}
791+			}
792+			current = "";
793+			continue;
794+		}
795+		current += s;
796+	}	
797+
798+	return deduplicated;
799+}
800+
801+class StyleHandler {
802+	private:
803+		// basemap: Maps baseparts to a index of the styles vector pointing to a Style object
804+		// because processing a CSS selector isn't trivial we want to make a short list of 
805+		// the styles that can possibly be a match so we aren't spending a lot of compute 
806+		// on a selector that applies to the wrong element. Todo this we take the right most part
807+		// of a selector (that is the part that targets the current element) and we do a few checks
808+		// like is the tagname the same, does it have that class, and do the id's match. If so that 
809+		// is a candidate and we run the full selector on it. That includes checking parents,children, 
810+		// or what ever else the selector needs to match.
811+		std::unordered_map<std::string, std::vector<size_t>> basemap;
812+		std::vector<Style> styles;
813+
814+		
815+		std::vector<std::string> parseNodeParts(Node* node) {
816+			std::vector<std::string> parts;
817+
818+			parts.push_back(node->getTagName());
819+			parts.push_back("#"+node->getId());
820+
821+			// classes and attributes
822+			auto keys = node->getAttributeKeys();
823+
824+			for (auto k : keys) {
825+				if (k != "tagName" || k != "id" || k != "innerText" || k != "class") {
826+					std::string v;
827+					std::string av = node->getAttribute(k);
828+					if (av != "") {
829+							v = "=\""+av+"\"";
830+
831+					}
832+					parts.push_back('['+k+v+']');
833+				}
834+			}
835+
836+			auto classes = node->classList.values();
837+
838+			for (auto c : classes) {
839+				parts.push_back("."+c);
840+			}
841+
842+			return parts;
843+		}
844+
845+		std::string cleanSelector(std::string selector) {
846+			std::string cleaned;
847+			for (size_t i = 0; i < selector.length(); i++) {
848+				if (selector[i] == ' ' && i > 0 && i < selector.length()-1) {
849+					char prev = selector[i-1];
850+					char next = selector[i+1];
851+
852+					if (prev != '>' && prev != '+' && prev != '~' && next != '>' && next != '+' && next != '~' && !std::isspace(prev) && !std::isspace(next)) {
853+						cleaned += selector[i];
854+					}
855+				} else if (selector[i] == ' ' && (i == 0 || i == selector.length()-1)) {
856+					continue;
857+				} else {
858+					cleaned += selector[i];
859+				}
860+			}
861+			return cleaned;
862+		}
863+	public:
864+		void add(Style style) {
865+			std::string selector = cleanSelector(style.selector);
866+			style.selector = selector;
867+			size_t index = styles.size();
868+
869+			style.index = index;
870+			styles.push_back(std::move(style));
871+
872+			std::vector<std::string> parts = parseSelectorParts(selector);
873+
874+			for (auto p : parts) {
875+				if (basemap.find(p) != basemap.end()) {
876+					basemap[p].push_back(index);
877+				} else {
878+					std::vector<size_t> bm = {index};
879+					basemap[p] = bm;
880+				}
881+			}
882+		}
883+		
884+		std::unordered_map<std::string, std::string> getStyles(Node* node) {
885+			std::vector<std::string> parts = parseNodeParts(node);
886+
887+			std::vector<size_t> canidates;
888+
889+			for (auto p : parts) {
890+				if (auto sty = basemap.find(p); sty != basemap.end()) {
891+					for (auto s : sty->second) {
892+						bool found = false;
893+						// This makes sure we aren't adding the same styles to the candidates list
894+						for (auto c : canidates) {
895+							if (c == s) {
896+								found = true;
897+								break;
898+							}
899+						}
900+						if (!found) {
901+							canidates.push_back(s);
902+						}
903+					}
904+				}
905+			}
906+
907+			// Now we have the list of candidates next we will refined our selection
908+			// we need to pull the selectors from each candidate then run testSelector on it
909+		}
910+};
911+
912+
913+
914+void popSelector(std::string selector, std::string& trimmed, std::string& popped, char& symbol) {
915+	for (size_t i = selector.length()-1; i >= 0; i--) {
916+		if (
917+				selector[i] == ' ' ||
918+				selector[i] == '>' ||
919+				selector[i] == '~' ||
920+				selector[i] == '+' 
921+		   ) {
922+			symbol = selector[i];
923+			trimmed = selector.substr(0,i);
924+			break;
925+		} else {
926+			popped = selector[i]+popped;
927+		}
928+	}
929+}
930+
931+
932+bool testSelector(Node* node, std::string selector) {
933+	// By the time we get here, we know the right most selector some what matches
934+	// so now if it is a compound selector we should see if the parent('s) some what match
935+	if (selector == "*") {
936+		return true;
937+	}
938+
939+	std::vector<std::string> selectors = splitSelector(selector, ',');
940+
941+	if (selectors.size() > 1) {
942+		for (auto s : selectors) {
943+			bool match = testSelector(node, s);
944+
945+			if (match) {
946+				return true;
947+			}
948+		}
949+	}
950+
951+	std::string trimmed, popped;
952+	char symbol;
953+	popSelector(selector, trimmed, popped, symbol);
954+
955+
956+	// Check if popped matches this element
957+	
958+	std::vector<std::string> pParts = parseSelectorParts(popped);
959+
960+	bool matched = true;
961+
962+	std::vector<std::string> nodeClasses = node->classList.values();
963+	std::unordered_map<std::string, std::string> attributes = node->getAttributes();
964+
965+
966+	std::vector<std::string> attrs;
967+
968+	for (auto a : attributes) {
969+		if (a.second != "") {
970+			attrs.push_back("["+a.first+"=\""+a.second+"\"]");
971+		} else {
972+
973+			attrs.push_back("["+a.first+"]");
974+		}
975+	}
976+
977+	for (size_t i = 0; i < pParts.size(); i++) {
978+		std::string p = pParts[i];
979+		if (i == 0 && p[0] != '#' && p[0] != '.' &&  p[0] != '[') {
980+			matched = node->getTagName() == p;
981+		} else if (p[0] == '#') {
982+			matched = node->getId() == p;
983+		} else if (p[0] == '.') {
984+			// Remove the period
985+			std::string clipped = p.substr(1);
986+			bool found = false;
987+			for (auto c : nodeClasses) {
988+				if (clipped == c) {
989+					found = true;
990+					break;
991+				}
992+			}
993+			matched = found;
994+		} else if (p[0] == '[') {
995+			bool found = false;
996+			for (auto a : attrs) {
997+				if (p == a) {
998+					found = true;
999+					break;
1000+				}
1001+			}
1002+			matched = found;
1003+		}
1004+
1005+		if (!matched) {
1006+			// Return out of the function
1007+			return false;
1008+		}
1009+	}
1010+
1011+	// The rest of the logic assumes the other selectors do not match until proven otherwise
1012+	matched = false;
1013+
1014+	if (symbol == '\0' || node->parent != nullptr) {
1015+		return false;
1016+	}
1017+	
1018+	if (symbol == '>') {
1019+		// We are just testing the parent node here
1020+		matched = testSelector(node->parent, trimmed);
1021+	} else if (symbol == '+') {
1022+		const auto& children = node->parent->children;
1023+		if (children.size() > 1) {
1024+			for (size_t i = 0; i < children.size(); i++) {
1025+				Node* child_ptr = children[i].get();
1026+
1027+				if (child_ptr == node) {
1028+					if (i == 0) {
1029+						return false;
1030+					} else {
1031+						// In this selector we just need to get the direct sibling and test it
1032+						matched = testSelector(children[i-1].get(), trimmed);
1033+					}
1034+					break;
1035+				}
1036+			}
1037+		}
1038+	} else if (symbol == '~') {
1039+		const auto& children = node->parent->children;
1040+		if (children.size() > 1) {
1041+			for (size_t i = 0; i < children.size(); i++) {
1042+				Node* child_ptr = children[i].get();
1043+
1044+				if (child_ptr == node) {
1045+					break;
1046+				} else {
1047+					// We have to test every selector before the current element,
1048+					// if any match then it matches
1049+					matched = testSelector(children[i].get(), trimmed);
1050+					if (matched) {
1051+						break;
1052+					}
1053+				}
1054+			}
1055+		}
1056+	} else if (symbol == ' ') {
1057+		// For the descendant combinator we have to check every parent until we match or nullptr out
1058+		Node* current = node->parent;
1059+		while (current != nullptr) {
1060+			if (testSelector(current, trimmed)) {
1061+				matched = true;
1062+				break;
1063+			}
1064+			current = current->parent;
1065+		}
1066+	}
1067+
1068+	return matched;
1069+}
1070+
1071+
1072diff --git a/tests/css_selector.cc b/tests/css_selector.cc
1073new file mode 100644
1074index 0000000..44a9589
1075--- /dev/null
1076+++ b/tests/css_selector.cc
1077@@ -0,0 +1,77 @@
1078+#include <catch_amalgamated.hpp>
1079+#include "../include/grim.h"
1080+#include <string>
1081+#include <unordered_set>
1082+#include <fstream>
1083+
1084+void checkSelectorParts(std::string selector, std::vector<std::vector<std::string>> expected) {
1085+	INFO("=========================");
1086+	INFO("Selector: "+selector+"\n");
1087+
1088+	std::vector<std::vector<std::string>> css = parseSelectorParts(selector);
1089+
1090+	REQUIRE(css.size() == expected.size());
1091+
1092+	std::string note = "";
1093+	for (size_t a = 0; a < css.size(); a++) {
1094+		std::string str = "{";
1095+		for (size_t b = 0; b < css[a].size(); b++) {
1096+			str += "\""+css[a][b]+"\",";
1097+		}
1098+		str += "},";
1099+
1100+		note += str;
1101+	}
1102+	INFO("PARSED: "+note);
1103+
1104+	for (size_t a = 0; a < css.size(); a++) {
1105+		REQUIRE(css[a].size() == expected[a].size());
1106+
1107+		for (size_t b = 0; b < css[a].size(); b++) {
1108+			REQUIRE(css[a][b] == expected[a][b]);
1109+		}
1110+	}
1111+
1112+	
1113+}
1114+
1115+TEST_CASE( "CSS Selection", "[css]" ) {
1116+	SECTION("parseSelectorParts can parse a simple CSS selector") {
1117+		// This function only parses the last part of the selector
1118+		checkSelectorParts("div > h1", {{"div", ">", "h1"}});
1119+		BENCHMARK("div > h1") {
1120+			return parseSelectorParts("div > h1");
1121+		};
1122+	}
1123+	SECTION("parseSelectorParts can parse a simple CSS selector with a psuedo class selector") {
1124+		checkSelectorParts("div > h1:checked", {{"div", ">", "h1",":checked"}});
1125+		BENCHMARK("div > h1:checked") {
1126+			return parseSelectorParts("div > h1:checked");
1127+		};
1128+	}
1129+	SECTION("parseSelectorParts can parse a attribute selector") {
1130+		checkSelectorParts("a[href=\"https://www.example.com/\"]", {{"a", "[href=\"https://www.example.com/\"]"}});
1131+		BENCHMARK("a[href=\"https://www.example.com/\"]") {
1132+			return parseSelectorParts("a[href=\"https://www.example.com/\"]");
1133+		};
1134+	}
1135+	SECTION("parseSelectorParts can parse has()") {
1136+		checkSelectorParts("h1:has(+ h2)", {{"h1",":has(+ h2)"}});
1137+		BENCHMARK("h1:has(+ h2)") {
1138+			return parseSelectorParts("h1:has(+ h2)");
1139+		};
1140+	}
1141+	
1142+	SECTION("parseSelectorParts can parse a complex where() selector") {
1143+		checkSelectorParts(":where(ol, ul, menu:unsupported)", {{":where(ol, ul, menu:unsupported)"}});
1144+		BENCHMARK(":where(ol, ul, menu:unsupported)") {
1145+			return parseSelectorParts(":where(ol, ul, menu:unsupported)");
1146+		};
1147+	}
1148+	SECTION("parseSelectorParts can parse multiple simple selectors"){
1149+		checkSelectorParts("input:required, div#id + h1.c1.c2, input[type='password'].class", {{"input", ":required"},{"div", "#id", "+", "h1", ".c1", ".c2"},{"input", "[type=\"password\"]", ".class"}});
1150+		BENCHMARK("input:required, div#id + h1.c1.c2, input[type='password'].class") {
1151+			return parseSelectorParts("input:required, div#id + h1.c1.c2, input[type='password'].class");
1152+		};
1153+	}
1154+}
1155diff --git a/tests/main.cc b/tests/html_parser.cc
1156similarity index 90%
1157rename from tests/main.cc
1158rename to tests/html_parser.cc
1159index e115601..ce55213 100644
1160--- a/tests/main.cc
1161+++ b/tests/html_parser.cc
1162@@ -43,6 +43,10 @@ TEST_CASE( "parseStream can parse various types of HTML strings", "[html]" ) {
1163 		std::stringstream ss(html);
1164 		std::unique_ptr<Node> document = parseStream(ss);
1165 
1166+		BENCHMARK("Simple HTML Document") {
1167+			return parseStream(ss);
1168+		};
1169+
1170 		// Ensure the only child of root is html
1171 		Node* current = document->children[0].get();
1172 
1173@@ -79,6 +83,10 @@ TEST_CASE( "parseStream can parse various types of HTML strings", "[html]" ) {
1174 		std::stringstream ss(html);
1175 		std::unique_ptr<Node> document = parseStream(ss);
1176 
1177+		BENCHMARK("Inline comment") {
1178+			return parseStream(ss);
1179+		};
1180+
1181 		// Ensure the only child of root is html
1182 		Node* current = document->children[0].get();
1183 
1184@@ -95,6 +103,10 @@ TEST_CASE( "parseStream can parse various types of HTML strings", "[html]" ) {
1185 		std::stringstream ss(html);
1186 		std::unique_ptr<Node> document = parseStream(ss);
1187 
1188+		BENCHMARK("Multiline comment") {
1189+			return parseStream(ss);
1190+		};
1191+
1192 		// Ensure the only child of root is html
1193 		Node* current = document->children[0].get();
1194 
1195@@ -111,6 +123,10 @@ TEST_CASE( "parseStream can parse various types of HTML strings", "[html]" ) {
1196 		std::stringstream ss(html);
1197 		std::unique_ptr<Node> document = parseStream(ss);
1198 
1199+		BENCHMARK("Multiline comment with HTML") {
1200+			return parseStream(ss);
1201+		};
1202+
1203 		// Ensure the only child of root is html
1204 		Node* current = document->children[0].get();
1205 
1206@@ -130,6 +146,10 @@ TEST_CASE( "parseStream can parse various types of HTML strings", "[html]" ) {
1207 		std::stringstream ss(html);
1208 		std::unique_ptr<Node> document = parseStream(ss);
1209 
1210+		BENCHMARK("Style tag parsing (with HTML in the content prop)") {
1211+			return parseStream(ss);
1212+		};
1213+
1214 		// Ensure the only child of root is html
1215 		Node* current = document->children[0].get();
1216 
1217@@ -157,6 +177,10 @@ TEST_CASE( "parseStream can parse various types of HTML strings", "[html]" ) {
1218 		std::stringstream ss(html);
1219 		std::unique_ptr<Node> document = parseStream(ss);
1220 
1221+		BENCHMARK("Script tag prematurly closing a pre tag") {
1222+			return parseStream(ss);
1223+		};
1224+
1225 		// Ensure the only child of root is html
1226 		Node* current = document->children[0].get();
1227 
1228@@ -177,6 +201,10 @@ TEST_CASE( "parseStream can parse various types of HTML strings", "[html]" ) {
1229 		std::stringstream ss(html);
1230 		std::unique_ptr<Node> document = parseStream(ss);
1231 
1232+		BENCHMARK("Textarea with script tag inside") {
1233+			return parseStream(ss);
1234+		};
1235+
1236 		// Ensure the only child of root is html
1237 		Node* current = document->children[0].get();
1238