home readme diff tree note docs

0commit 6c3ae0e31eb0893f20e3872117f92cc6b9a942af
1Author: Mason Wright <mason@lakefox.net>
2Date:   Thu Jul 3 14:50:33 2025 -0600
3
4    Random
5
6diff --git a/include/grim.h b/include/grim.h
7index edae26d..3e87987 100644
8--- a/include/grim.h
9+++ b/include/grim.h
10@@ -9,85 +9,89 @@
11 #include <memory>
12 
13 struct Styles {
14-    std::vector<std::unordered_map<std::string, std::string>> stylesheets;
15-    std::unordered_map<std::string, std::string> inlineStyles;
16-    std::unordered_map<std::string, std::unordered_map<std::string, std::string>> psuedoStyles;
17+	std::vector<std::unordered_map<std::string, std::string>> stylesheets;
18+	std::unordered_map<std::string, std::string> inlineStyles;
19+	std::unordered_map<std::string, std::unordered_map<std::string, std::string>> psuedoStyles;
20 };
21 
22 struct Bounds {
23-    int top;
24-    int right;
25-    int bottom;
26-    int left;
27-};
28-
29-struct State {
30-    int width;
31-    int height;
32-    int z;
33-    bool hidden;
34-    int tabIndex;
35-};
36-
37-class ClassList {
38-private:
39-    std::vector<std::string> classes;
40-public:
41-    std::string value() const;
42-    std::vector<std::string> values();
43-    void add(std::string value);
44-    void remove(std::string value);
45+	int top;
46+	int right;
47+	int bottom;
48+	int left;
49 };
50 
51 class Node {
52-private:
53-    std::string TagName;
54-    std::unordered_map<std::string, std::string> Attributes;
55-
56-    template<typename T>
57-    void setAttribute(const std::string& name, const T& value);
58-
59-    template<typename T>
60-    T getAttribute(const std::string& name) const;
61-
62-public:
63-    Node* parent;
64-    std::vector<std::unique_ptr<Node>> children;
65-    ClassList classList;
66-
67-    Node();
68-    std::string getTagName() const;
69-    void setTagName(const std::string& name);
70-    const std::unordered_map<std::string, std::string>& getAttributes() const;
71-
72-    // --- Define Getters and Setters
73-    // !TODO: Add all global attributes
74-    // + Src: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes
75-    // The macro should stay here as it's part of the class definition
76-    #define GENERATE_ATTRIBUTE_ACCESSORS(_Type, _FuncNameSuffix, _AttrKeyString) \
77-        _Type get##_FuncNameSuffix() const; \
78-        void set##_FuncNameSuffix(_Type value);
79-
80-    GENERATE_ATTRIBUTE_ACCESSORS(std::string, Id, "id")
81-    GENERATE_ATTRIBUTE_ACCESSORS(std::string, InnerText, "innerText")
82-    GENERATE_ATTRIBUTE_ACCESSORS(bool, ContentEditable, "contenteditable")
83-    GENERATE_ATTRIBUTE_ACCESSORS(std::string, Href, "href")
84-    GENERATE_ATTRIBUTE_ACCESSORS(std::string, Src, "src")
85-    GENERATE_ATTRIBUTE_ACCESSORS(std::string, Title, "title")
86-    GENERATE_ATTRIBUTE_ACCESSORS(std::string, Value, "value")
87-    GENERATE_ATTRIBUTE_ACCESSORS(int, TabIndex, "tabindex")
88-    GENERATE_ATTRIBUTE_ACCESSORS(bool, Disabled, "disabled")
89-    GENERATE_ATTRIBUTE_ACCESSORS(bool, Required, "required")
90-    GENERATE_ATTRIBUTE_ACCESSORS(bool, Checked, "checked")
91-
92-    Node* createElement(std::string name);
93-
94-    void setAttribute(const std::string& name, const std::string& value);
95-    std::string getAttribute(const std::string& name) const;
96-
97-    std::vector<std::string> getAttributeKeys();
98-
99-    std::string print(int indent = 0);
100+	private:
101+		std::string TagName;
102+		std::unordered_map<std::string, std::string> Attributes;
103+
104+		template<typename T>
105+			void setAttribute(const std::string& name, const T& value);
106+
107+		template<typename T>
108+			T getAttribute(const std::string& name) const;
109+
110+	public:
111+		Node* parent;
112+		std::vector<std::unique_ptr<Node>> children;
113+		struct ClassList {
114+			private:
115+				Node* self;
116+				std::vector<std::pair<int, int>> indexes;
117+				size_t len;
118+				// No return, modifies indexes directly
119+				void createIndex();
120+				bool checkIndex();
121+
122+			public:
123+				ClassList(Node* node) : self(node) {}
124+				std::string value() const;
125+				void add(std::string value);
126+				void remove(std::string value);
127+				void toggle(std::string value);
128+				void replace(std::string key, std::string value);
129+				bool contains(std::string value);
130+				std::string item(size_t key);
131+				size_t length();
132+		};
133+
134+		ClassList classList;
135+
136+		Node() : parent(nullptr), classList(this) {
137+		}
138+		std::string getTagName() const;
139+		void setTagName(const std::string& name);
140+		const std::unordered_map<std::string, std::string>& getAttributes() const;
141+
142+		// --- Define Getters and Setters
143+		// !TODO: Add all global attributes
144+		// + Src: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes
145+		// The macro should stay here as it's part of the class definition
146+#define GENERATE_ATTRIBUTE_ACCESSORS(_Type, _FuncNameSuffix, _AttrKeyString) \
147+		_Type get##_FuncNameSuffix() const; \
148+		void set##_FuncNameSuffix(_Type value);
149+
150+		GENERATE_ATTRIBUTE_ACCESSORS(std::string, Id, "id")
151+			GENERATE_ATTRIBUTE_ACCESSORS(std::string, InnerText, "innerText")
152+			GENERATE_ATTRIBUTE_ACCESSORS(bool, ContentEditable, "contenteditable")
153+			GENERATE_ATTRIBUTE_ACCESSORS(std::string, Href, "href")
154+			GENERATE_ATTRIBUTE_ACCESSORS(std::string, Src, "src")
155+			GENERATE_ATTRIBUTE_ACCESSORS(std::string, Title, "title")
156+			GENERATE_ATTRIBUTE_ACCESSORS(std::string, Value, "value")
157+			GENERATE_ATTRIBUTE_ACCESSORS(int, TabIndex, "tabindex")
158+			GENERATE_ATTRIBUTE_ACCESSORS(bool, Disabled, "disabled")
159+			GENERATE_ATTRIBUTE_ACCESSORS(bool, Required, "required")
160+			GENERATE_ATTRIBUTE_ACCESSORS(bool, Checked, "checked")
161+
162+			Node* createElement(std::string name);
163+
164+		void setAttribute(const std::string& name, const std::string& value);
165+		std::string getAttribute(const std::string& name) const;
166+
167+		std::vector<std::string> getAttributeKeys();
168+
169+		std::string print(int indent = 0);
170 };
171 
172 /*!
173@@ -95,7 +99,7 @@ public:
174  *
175  * @param selector A CSS selector
176  * @return 
177-*/
178+ */
179 std::vector<std::vector<std::string>> parseSelectorParts(std::string);
180 
181 bool testSelector(Node*, std::vector<std::vector<std::string>>);
182diff --git a/src/grim.cc b/src/grim.cc
183index 1dcde70..2911970 100755
184--- a/src/grim.cc
185+++ b/src/grim.cc
186@@ -631,11 +631,33 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
187 			}
188 			matched = found;
189 		} // !TODO: Need a case for : , this will run the :has... logic
190+		else if (p[0] == ":") {
191+			// This handles the relative pseudo classes
192+			for :last-child and nth-child and the others we should get the position
193+				     of the node at the top. Then apply in attributes
194+			// These will run through, parsing the inner part and recalling testSelector
195+			// with it
196+			if (p.starts_with(":has(")) {
197+				
198+			} else if (p.starts_with(":last-child")) {
199+			} else if (p.starts_with(":first-child")) {
200+				
201+			} else if (p.starts_with(":where(")) {
202+				// where
203+			} else if (p.starts_with(":is(")) {
204+
205+			} else if (p.starts_with(":not(")) {
206+			} else if (p.starts_with(":nth-child(")) {
207+			} else if (p.starts_with("::before")) {
208+			} else if (p.starts_with("::after")) {}
209+				
210+		}
211 
212 		if (!matched) {
213 			// Return out of the function
214 			return false;
215 		}
216+		i++;
217 	}
218 
219 	// if the node did not match it would have returned false so if there isn't 
220@@ -658,20 +680,13 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
221 		matched = testSelector(node->parent, {trimmed});
222 	} else if (symbol == "+") {
223 		// next sibling combinator
224-		const auto& children = node->parent->children;
225 		if (children.size() > 1) {
226-			for (size_t i = 0; i < children.size(); i++) {
227-				Node* child_ptr = children[i].get();
228-
229-				if (child_ptr == node) {
230-					if (i == 0) {
231-						return false;
232-					} else {
233-						// In this selector we just need to get the direct sibling and test it
234-						matched = testSelector(children[i-1].get(), {trimmed});
235-					}
236-					break;
237-				}
238+			// If there isn't a prevous sibling then it cannot match
239+			if (childIndex == 0) {
240+				return false;
241+			} else {
242+				// In this selector we just need to get the direct sibling and test it
243+				matched = testSelector(children[i-1].get(), {trimmed});
244 			}
245 		}
246 	} else if (symbol == "~") {
247diff --git a/tests/css_selector.cc b/tests/css_selector.cc
248index d2f8ea9..25a89cd 100644
249--- a/tests/css_selector.cc
250+++ b/tests/css_selector.cc
251@@ -86,6 +86,12 @@ TEST_CASE( "CSS Selection", "[css]" ) {
252 			return parseSelectorParts("div h1");
253 		};
254 	}
255+	SECTION("parseSelectorParts can parse a selector with only a id") {
256+		checkSelectorParts("#id", {{"#id"}});
257+		BENCHMARK("#id") {
258+			return parseSelectorParts("#id");
259+		};
260+	}
261 }
262 
263 TEST_CASE("testSelector", "[CSS]") {
264@@ -96,6 +102,7 @@ TEST_CASE("testSelector", "[CSS]") {
265 					"<link rel=\"stylesheet\" href=\'/style.css\'/>"
266 				"</head>"
267 				"<body>"
268+					"<h1 id=\"h1Tag\">Header</h1>"
269 					"<p id=\"paragraph\" class=\"class1 class2\">This is some text for a simple test</p>"
270 				"</body>"
271 			"</html>";
272@@ -104,12 +111,31 @@ TEST_CASE("testSelector", "[CSS]") {
273 	std::stringstream ss(html);
274 	std::unique_ptr<Node> document = parseStream(ss);
275 
276-	std::cout << document->getTagName() << std::endl;
277-
278 	SECTION("testSelector can match a element given it's tagName") {
279 		auto html = document->children[0].get();
280-		INFO(html->getTagName());
281-		bool match = testSelector(html, parseSelectorParts("nothtml"));
282-		REQUIRE(match);
283+		REQUIRE(testSelector(html, parseSelectorParts("html")));
284+		REQUIRE_FALSE(testSelector(html, parseSelectorParts("h1")));
285+	}
286+	SECTION("testSelector can match a element by it's attributes only") {
287+		auto link = document->children[0]->children[0]->children[1].get();
288+		REQUIRE(testSelector(link, parseSelectorParts("[rel=\"stylesheet\"]")));
289+	}
290+	SECTION("testSelector can match a element by it's attributes and tagName") {
291+		auto link = document->children[0]->children[0]->children[1].get();
292+		REQUIRE(testSelector(link, parseSelectorParts("link[rel=\"stylesheet\"]")));
293+		REQUIRE_FALSE(testSelector(link, parseSelectorParts("title[rel=\"stylesheet\"]")));
294+	}
295+	SECTION("testSelector can match a element by it's id only") {
296+		auto h1 = document->children[0]->children[1]->children[0].get();
297+		REQUIRE(testSelector(h1, parseSelectorParts("#h1Tag")));
298+	}
299+	SECTION("testSelector can match a element by it's id and tagName") {
300+		auto h1 = document->children[0]->children[1]->children[0].get();
301+		REQUIRE(testSelector(h1, parseSelectorParts("h1#h1Tag")));
302+	}
303+	SECTION("testSelector can match a element by it's class and not matach on a non class") {
304+		auto p = document->children[0]->children[1]->children[1].get();
305+		REQUIRE(testSelector(p, parseSelectorParts(".class1")));
306+		REQUIRE_FALSE(testSelector(p, parseSelectorParts(".class3")));
307 	}
308 }
309diff --git a/tests/html_node.cc b/tests/html_node.cc
310new file mode 100644
311index 0000000..f4b48c5
312--- /dev/null
313+++ b/tests/html_node.cc
314@@ -0,0 +1,71 @@
315+#include <catch_amalgamated.hpp>
316+#include "../include/grim.h"
317+#include "../include/parser.h" 
318+#include <string>
319+#include <fstream>
320+#include <iostream>
321+
322+
323+TEST_CASE("ClassList", "[html]") {
324+	std::string html = "<div class=\"class1  class2 class3\"></div>";
325+
326+	std::stringstream ss(html);
327+	std::unique_ptr<Node> document = parseStream(ss);
328+	auto div = document->children[0].get();
329+
330+	SECTION("ClassList can get the length and access items correctly") {
331+		INFO(div->getAttribute("class"));
332+
333+		REQUIRE(div->classList.length() == 3);
334+		REQUIRE(div->classList.item(0) == "class1");
335+		REQUIRE(div->classList.item(1) == "class2");
336+		REQUIRE(div->classList.item(2) == "class3");
337+	}
338+
339+	SECTION("ClassList can add a class and get a class") {
340+		div->classList.add("class4");
341+
342+		INFO(div->getAttribute("class"));
343+		REQUIRE(div->classList.length() == 4);
344+		REQUIRE(div->classList.item(0) == "class1");
345+		REQUIRE(div->classList.item(3) == "class4");
346+	}
347+
348+	SECTION("ClassList can remove a class") {
349+		div->classList.remove("class2");
350+
351+		INFO(div->getAttribute("class"));
352+		REQUIRE(div->classList.length() == 2);
353+		REQUIRE(div->classList.item(0) == "class1");
354+		REQUIRE(div->classList.item(1) == "class3");
355+	}
356+
357+	SECTION("ClassList can check if a class is contained in the class list") {
358+		REQUIRE(div->classList.contains("class1"));
359+		REQUIRE(div->classList.contains("class2"));
360+		REQUIRE(div->classList.contains("class3"));
361+		REQUIRE_FALSE(div->classList.contains("class4"));
362+	}
363+
364+	SECTION("ClassList can toggle a class") {
365+		REQUIRE(div->classList.length() == 3);
366+
367+		div->classList.toggle("class4");
368+
369+		REQUIRE(div->classList.length() == 4);
370+
371+		div->classList.toggle("class4");
372+
373+		INFO(div->getAttribute("class"));
374+		REQUIRE(div->classList.length() == 3);
375+	}
376+
377+	SECTION("ClassList benchmarks") {
378+		BENCHMARK("Getting length") {
379+			return div->classList.length();
380+		};
381+		BENCHMARK("Getting a item") {
382+			return div->classList.item(1);
383+		};
384+	}
385+}