home readme diff tree note docs

0commit 350e7d88bb2feb9db00c6e032cc6623f215b7adf
1Author: Mason Wright <mason@lakefox.net>
2Date:   Thu Jul 3 14:48:07 2025 -0600
3
4    ClassList
5
6diff --git a/Makefile b/Makefile
7index 31db22a..149b1df 100644
8--- a/Makefile
9+++ b/Makefile
10@@ -25,6 +25,9 @@ build/parser.o: src/parser.cc include/parser.h | build
11 run_tests: build/html_parser_test
12 	build/html_parser_test
13 
14+build/html_node_test: tests/html_node.cc build/grim.o build/catch.o build/parser.o | build
15+	${CC} ${CCFLAGS} -Iinclude/ tests/html_node.cc build/grim.o build/catch.o build/parser.o -o build/html_node_test 
16+
17 build/css_selector_test: tests/css_selector.cc build/grim.o build/catch.o build/parser.o | build
18 	${CC} ${CCFLAGS} -Iinclude/ tests/css_selector.cc build/grim.o build/catch.o build/parser.o -o build/css_selector_test 
19 
20diff --git a/src/grim.cc b/src/grim.cc
21index f674f6f..1dcde70 100755
22--- a/src/grim.cc
23+++ b/src/grim.cc
24@@ -4,36 +4,148 @@
25 #include <algorithm>
26 #include <stdexcept>
27 #include <cctype>
28+#include <string_view>
29 
30-std::string ClassList::value() const {
31-    if (classes.empty()) {
32-        return "";
33-    }
34-    std::string collection = classes[0];
35-    for (size_t i = 1; i < classes.size(); ++i) {
36-        collection += " " + classes[i];
37-    }
38-    return collection;
39+// Constructor
40+
41+void Node::ClassList::createIndex() {
42+	int index = 0;
43+	// Add a space to the back to help trigger the adding of the 
44+	// last item
45+	std::string classes = self->Attributes["class"]+" ";
46+	// Update the stored length
47+	len = classes.length();
48+	indexes.clear();
49+
50+	bool waitForFirstChar = true;
51+
52+	for (size_t i = 0; i < len; i++) {
53+		if (waitForFirstChar && classes[i] != ' ') {
54+			index = i;
55+			waitForFirstChar = false;
56+		} else if (!waitForFirstChar) {
57+			if (classes[i] == ' ') {
58+				indexes.push_back({index, i-index});
59+				waitForFirstChar = true;
60+			}
61+		}
62+	}
63+}
64+
65+
66+bool Node::ClassList::checkIndex() {
67+	std::string classes = self->Attributes["class"];
68+	if (classes.length() != len) {
69+		return false;
70+	} else {
71+		// Skip the first one because there is a space
72+		// also we don't care if the values are the same
73+		// just that the array indexing is the same. If the 
74+		// value is the same size and position it doesn't matter
75+		for (size_t i = 1; i < indexes.size(); i++) {
76+			// See if the index position is a space
77+			// if not return false
78+			if (classes[indexes[i-1].first] != ' ') {
79+				return false;
80+			}
81+		}
82+		return true;
83+	}
84 }
85 
86-std::vector<std::string> ClassList::values() {
87-	return classes;
88+size_t Node::ClassList::length() {
89+	if (!checkIndex()) {
90+		createIndex();
91+	}
92+	return indexes.size();
93 }
94 
95-void ClassList::add(std::string value) {
96-    classes.push_back(value);
97+
98+std::string Node::ClassList::item(size_t key) {
99+	if (!checkIndex()) {
100+		createIndex();
101+	}
102+	if (key < indexes.size()) {
103+		std::pair<int, int> pair = indexes[key];
104+		return self->Attributes["class"].substr(pair.first, pair.second);
105+	} else {
106+		return "";
107+	}
108 }
109 
110-void ClassList::remove(std::string value) {
111-    auto it_prev = std::find(classes.begin(), classes.end(), value);
112-    if (it_prev != classes.end()) {
113-        *it_prev = classes.back();
114-        classes.pop_back();
115-    }
116+void Node::ClassList::add(std::string value) {
117+	if (!checkIndex()) {
118+		createIndex();
119+	}
120+
121+	if (self->Attributes["class"].length() == 0) {
122+		self->Attributes["class"] += value;
123+	} else {
124+		self->Attributes["class"] += " "+value;
125+	}
126+	// len because the index of the added space will be the length of 
127+	// the prevous string
128+	int vl = value.length();
129+	indexes.push_back({len, vl});
130+	len += vl+1;
131 }
132 
133-// Constructor
134-Node::Node() : parent(nullptr) {}
135+void Node::ClassList::remove(std::string value) {
136+	if (!checkIndex()) {
137+		createIndex();
138+	}
139+
140+	int newLen = value.length();
141+	size_t cLen = indexes.size();
142+	std::string classes = self->Attributes["class"];	
143+
144+	for (size_t i = 0; i < cLen; i++) {
145+		std::pair<int, int> pair = indexes[i];
146+		if (newLen == pair.second) {
147+			if (classes.substr(pair.first, pair.second) == value) {
148+				// Splice out the value to be removed 
149+				// !ISSUE: This will keep the surounding spaces and cause the size to grow when 
150+				// + classes are added and removed
151+				self->Attributes["class"] = classes.substr(0, pair.first)+classes.substr(pair.first+pair.second);
152+			}
153+		}
154+	}	
155+}
156+
157+bool Node::ClassList::contains(std::string value) {
158+	if (!checkIndex()) {
159+		createIndex();
160+	}
161+
162+	int newLen = value.length();
163+	size_t cLen = indexes.size();
164+	std::string classes = self->Attributes["class"];	
165+	bool found = false;
166+
167+	for (size_t i = 0; i < cLen; i++) {
168+		std::pair<int, int> pair = indexes[i];
169+		if (newLen == pair.second) {
170+			if (classes.substr(pair.first, pair.second) == value) {
171+				// Same logic as remove but turns found true then breaks
172+				found = true;
173+				break;
174+			}
175+		}
176+	}
177+	return found;
178+}
179+
180+void Node::ClassList::toggle(std::string value) {
181+	if (contains(value)) {
182+		remove(value);
183+	} else {
184+		add(value);
185+	}
186+}
187+
188+std::string Node::ClassList::value() const {
189+	return self->Attributes["class"];
190+}
191 
192 std::string Node::getTagName() const { return TagName; }
193 void Node::setTagName(const std::string& name) { TagName = name; }
194@@ -350,10 +462,10 @@ class StyleHandler {
195 				}
196 			}
197 
198-			auto classes = node->classList.values();
199+			auto classLen = node->classList.length();
200 
201-			for (auto c : classes) {
202-				parts.push_back("."+c);
203+			for (size_t i = 0; i < classLen; i++) {
204+				parts.push_back("."+node->classList.item(i));
205 			}
206 
207 			return parts;
208@@ -457,7 +569,13 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
209 	// returns if matched is false
210 	bool matched = true;
211 
212-	std::vector<std::string> nodeClasses = node->classList.values();
213+	std::vector<std::string> nodeClasses;
214+	size_t classLen = node->classList.length();
215+	for (size_t i = 0; i < classLen; i++) {
216+		nodeClasses.push_back("."+node->classList.item(i));
217+	}
218+	
219+
220 	std::unordered_map<std::string, std::string> attributes = node->getAttributes();
221 	std::vector<std::string> attrs;
222 
223@@ -469,26 +587,41 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
224 			attrs.push_back("["+a.first+"]");
225 		}
226 	}
227-	
228+
229+	// Find the index of the child relative to it's parent
230+	const auto& children = node->parent->children;
231+	size_t childIndex = 0;
232+	if (children.size() > 1) {
233+		for (size_t i = 0; i < children.size(); i++) {
234+			Node* child_ptr = children[i].get();
235+
236+			if (child_ptr == node) {
237+				childIndex = i;	
238+			}
239+		}
240+	}
241+
242+
243 	size_t i = 0;
244 	for (auto p : parts) {
245-		i++;
246 		if (i == 0 && p[0] != '#' && p[0] != '.' &&  p[0] != '[') {
247 			matched = node->getTagName() == p;
248 		} else if (p[0] == '#') {
249-			matched = node->getId() == p;
250+			matched = "#"+node->getId() == p;
251 		} else if (p[0] == '.') {
252-			// Remove the period
253-			std::string clipped = p.substr(1);
254 			bool found = false;
255 			for (auto c : nodeClasses) {
256-				if (clipped == c) {
257+				if (p == c) {
258 					found = true;
259 					break;
260 				}
261 			}
262 			matched = found;
263 		} else if (p[0] == '[') {
264+			// This covers all attributes and non relative pseudo-classes
265+			// as parseSelectorParts converts any attribute
266+			// without a ending ) to [name]: 
267+			// See tests/css_selector.cc for examples
268 			bool found = false;
269 			for (auto a : attrs) {
270 				if (p == a) {
271diff --git a/tests/css_selector.cc b/tests/css_selector.cc
272index ab00d29..d2f8ea9 100644
273--- a/tests/css_selector.cc
274+++ b/tests/css_selector.cc
275@@ -3,7 +3,6 @@
276 #include "../include/parser.h" 
277 #include <string>
278 #include <fstream>
279-#include <iostream>
280 
281 void checkSelectorParts(std::string selector, std::vector<std::vector<std::string>> expected) {
282 	INFO("=========================");