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+}