home
readme
diff
tree
note
docs
0commit 8f2e83556d12aaebe8e8597ea6923804b0eb7a43
1Author: Mason Wright <mason@lakefox.net>
2Date: Tue Jun 24 20:39:35 2025 -0600
3
4 Added direct node matching to testSelector
5
6diff --git a/include/grim.h b/include/grim.h
7index 05f148b..239dc8f 100644
8--- a/include/grim.h
9+++ b/include/grim.h
10@@ -86,7 +86,7 @@ public:
11
12 std::vector<std::string> getAttributeKeys();
13
14- void print(int indent = 0) const;
15+ std::string print(int indent = 0);
16 };
17
18 #endif // NODE_H
19diff --git a/src/grim.cc b/src/grim.cc
20index 7f8677d..6600855 100755
21--- a/src/grim.cc
22+++ b/src/grim.cc
23@@ -145,37 +145,40 @@ std::vector<std::string> Node::getAttributeKeys() {
24 return keys;
25 }
26
27-void Node::print(int indent) const {
28+std::string Node::print(int indent) {
29+ std::string out = "";
30 for (int i = 0; i < indent; ++i) {
31- std::cout << " ";
32+ out += " ";
33 }
34
35- std::cout << "<" << getTagName();
36+ out += "<" + getTagName();
37 for (const auto& attr_pair : getAttributes()) {
38 if (attr_pair.first == "innerText" || attr_pair.first == "tagName") {
39 continue;
40 }
41- std::cout << " " << attr_pair.first;
42+ out += " " + attr_pair.first;
43 if (!attr_pair.second.empty()) {
44- std::cout << "=\"" << attr_pair.second << "\"";
45+ out += "=\"" + attr_pair.second + "\"";
46 }
47 }
48- std::cout << ">";
49+ out += ">";
50
51 if (!getAttribute("innerText").empty()) {
52- std::cout << "\n" << getAttribute("innerText");
53+ out += "\n" + getAttribute("innerText");
54 }
55
56- std::cout << std::endl;
57+ out += "\n";
58
59 for (const auto& child : children) {
60- child->print(indent + 1);
61+ out += child->print(indent + 1)+"\n";
62 }
63
64 for (int i = 0; i < indent; ++i) {
65- std::cout << " ";
66+ out += " ";
67 }
68- std::cout << "</" << getTagName() << ">" << std::endl;
69+ out += "</" + getTagName() + ">\n";
70+
71+ return out;
72 }
73
74 struct Style {
75@@ -192,122 +195,129 @@ struct Style {
76 // std::unordered_map<std::string, std::string> attributes;
77 // }
78
79-class StyleHandler {
80- private:
81- // basemap: Maps baseparts to a index of the styles vector pointing to a Style object
82- // because processing a CSS selector isn't trivial we want to make a short list of
83- // the styles that can possibly be a match so we aren't spending a lot of compute
84- // on a selector that applies to the wrong element. Todo this we take the right most part
85- // of a selector (that is the part that targets the current element) and we do a few checks
86- // like is the tagname the same, does it have that class, and do the id's match. If so that
87- // is a candidate and we run the full selector on it. That includes checking parents,children,
88- // or what ever else the selector needs to match.
89- std::unordered_map<std::string, std::vector<size_t>> basemap;
90- std::vector<Style> styles;
91-
92- std::vector<std::string> parseSelectorParts(std::string selector) {
93- // we want to find the right most (right of a " " > ~ + ) selector
94- // account for * for commas split the selector then for each parse right
95-
96- std::vector<std::string> parts;
97- std::string word;
98+std::vector<std::string> parseSelectorParts(std::string selector) {
99+ // we want to find the right most (right of a " " > ~ + ) selector
100+ // account for * for commas split the selector then for each parse right
101
102- for (size_t s = 0; s < selector.length(); s++) {
103- // Start parsing if we see a comma or the end of the selector
104- word += selector[s];
105+ std::vector<std::string> parts;
106+ std::string word;
107
108- if (selector[s] == ',' || s == selector.length()-1) {
109- if (selector[s] == ',') {
110- word.pop_back(); // Remove the trailing comma
111- }
112- // Break the tag and add the right most parts to parts
113- for (int i = word.length()-1; i > -1; i--) {
114- if (
115- word[i] == ' ' ||
116- word[i] == '>' ||
117- word[i] == '~' ||
118- word[i] == '+'
119- ) {
120- word = word.substr(i+1);
121- break;
122- }
123- }
124+ for (size_t s = 0; s < selector.length(); s++) {
125+ // Start parsing if we see a comma or the end of the selector
126+ word += selector[s];
127
128- // Now word contains the right most selection
129- // we will extract the quick to verify parts from it
130- // so we can build the basemap
131- std::string part;
132- bool inbracket = false;
133- for (auto w : word) {
134- // if the current letter is a : that is either a pseudo element
135- // or selector. We don't care about that part for this part
136- if (w == ':' && !inbracket) {
137- break;
138- }
139- // We don't care if the thing we are splitting is a id or class we just
140- // need them split up so we can use them as map keys
141-
142- if (w == '#' && !inbracket) {
143- if (part != "") {
144- parts.push_back(part);
145- part = "";
146- }
147-
148- } else if (w == '.' && !inbracket) {
149- if (part != "") {
150- parts.push_back(part);
151- part = "";
152- }
153- } else if (w == '[' || w == ']') {
154- if (part != "" && inbracket) {
155- parts.push_back('['+part+']');
156- part = "";
157- } else if (!inbracket) {
158- parts.push_back(part);
159- part = "";
160- }
161+ if (selector[s] == ',' || s == selector.length()-1) {
162+ if (selector[s] == ',') {
163+ word.pop_back(); // Remove the trailing comma
164+ }
165+ // Break the tag and add the right most parts to parts
166+ for (int i = word.length()-1; i > -1; i--) {
167+ if (
168+ word[i] == ' ' ||
169+ word[i] == '>' ||
170+ word[i] == '~' ||
171+ word[i] == '+'
172+ ) {
173+ word = word.substr(i+1);
174+ break;
175+ }
176+ }
177
178- if (w=='[') {
179- inbracket = true;
180- } else if (w == ']'){
181- inbracket = false;
182- }
183- continue;
184- }
185+ // Now word contains the right most selection
186+ // we will extract the quick to verify parts from it
187+ // so we can build the basemap
188+ std::string part;
189+ bool inbracket = false;
190+ for (auto w : word) {
191+// Need to add :checked etc parsing
192+ if (w == ':' && !inbracket) {
193+ break;
194+ }
195+ // We don't care if the thing we are splitting is a id or class we just
196+ // need them split up so we can use them as map keys
197
198- part += w;
199+ if (w == '#' && !inbracket) {
200+ if (part != "") {
201+ parts.push_back(part);
202+ part = "";
203 }
204
205+ } else if (w == '.' && !inbracket) {
206 if (part != "") {
207 parts.push_back(part);
208+ part = "";
209 }
210+ } else if (w == '[' || w == ']') {
211+
212+ // We need to check if its in brackets or not because the [ will
213+ // close the last .class, if we don't check then it will be in brackets
214+ if (part != "" && inbracket) {
215+ parts.push_back('['+part+']');
216+ part = "";
217+ } else if (!inbracket) {
218+ parts.push_back(part);
219+ part = "";
220+ }
221+
222+ if (w=='[') {
223+ inbracket = true;
224+ } else if (w == ']'){
225+ inbracket = false;
226+ }
227+ continue;
228+ }
229+
230+ // We need to keep a consistant quote mark to make matching reliable
231+ if (inbracket && w == '\'') {
232+ w = '"';
233 }
234+
235+ part += w;
236 }
237
238- std::vector<std::string> deduplicated;
239+ if (part != "") {
240+ parts.push_back(part);
241+ }
242+ }
243+ }
244
245- for (size_t p1 = 0; p1 < parts.size(); p1++) {
246- bool matches = false;
247+ std::vector<std::string> deduplicated;
248
249- if (parts[p1] == "") {
250- continue;
251- }
252+ for (size_t p1 = 0; p1 < parts.size(); p1++) {
253+ bool matches = false;
254
255- for (size_t p2 = p1+1; p2 < parts.size(); p2++) {
256- if (parts[p1] == parts[p2]) {
257- matches = true;
258- }
259- }
260+ if (parts[p1] == "") {
261+ continue;
262+ }
263
264- if (!matches) {
265- deduplicated.push_back(parts[p1]);
266- }
267+ for (size_t p2 = p1+1; p2 < parts.size(); p2++) {
268+ if (parts[p1] == parts[p2]) {
269+ matches = true;
270 }
271+ }
272
273- return deduplicated;
274+ if (!matches) {
275+ deduplicated.push_back(parts[p1]);
276 }
277+ }
278
279+ return deduplicated;
280+}
281
282+class StyleHandler {
283+ private:
284+ // basemap: Maps baseparts to a index of the styles vector pointing to a Style object
285+ // because processing a CSS selector isn't trivial we want to make a short list of
286+ // the styles that can possibly be a match so we aren't spending a lot of compute
287+ // on a selector that applies to the wrong element. Todo this we take the right most part
288+ // of a selector (that is the part that targets the current element) and we do a few checks
289+ // like is the tagname the same, does it have that class, and do the id's match. If so that
290+ // is a candidate and we run the full selector on it. That includes checking parents,children,
291+ // or what ever else the selector needs to match.
292+ std::unordered_map<std::string, std::vector<size_t>> basemap;
293+ std::vector<Style> styles;
294+
295+
296 std::vector<std::string> parseNodeParts(Node* node) {
297 std::vector<std::string> parts;
298
299@@ -473,10 +483,11 @@ void popSelector(std::string selector, std::string& trimmed, std::string& popped
300 }
301 }
302
303+
304 bool testSelector(Node* node, std::string selector) {
305 // By the time we get here, we know the right most selector some what matches
306 // so now if it is a compound selector we should see if the parent('s) some what match
307- if (selector == '*') {
308+ if (selector == "*") {
309 return true;
310 }
311
312@@ -498,21 +509,72 @@ bool testSelector(Node* node, std::string selector) {
313
314
315 // Check if popped matches this element
316- if () {
317- } else {
318- return false;
319+
320+ std::vector<std::string> pParts = parseSelectorParts(popped);
321+
322+ bool matched = true;
323+
324+ std::vector<std::string> nodeClasses = node->classList.values();
325+ std::unordered_map<std::string, std::string> attributes = node->getAttributes();
326+
327+
328+ std::vector<std::string> attrs;
329+
330+ for (auto a : attributes) {
331+ if (a.second != "") {
332+ attrs.push_back("["+a.first+"=\""+a.second+"\"]");
333+ } else {
334+
335+ attrs.push_back("["+a.first+"]");
336+ }
337 }
338
339- if (symbol == '' || node->parent != nullptr) {
340- return false;
341+ for (size_t i = 0; i < pParts.size(); i++) {
342+ std::string p = pParts[i];
343+ if (i == 0 && p[0] != '#' && p[0] != '.' && p[0] != '[') {
344+ matched = node->getTagName() == p;
345+ } else if (p[0] == '#') {
346+ matched = node->getId() == p;
347+ } else if (p[0] == '.') {
348+ // Remove the period
349+ std::string clipped = p.substr(1);
350+ bool found = false;
351+ for (auto c : nodeClasses) {
352+ if (clipped == c) {
353+ found = true;
354+ break;
355+ }
356+ }
357+ matched = found;
358+ } else if (p[0] == '[') {
359+ bool found = false;
360+ for (auto a : attrs) {
361+ if (p == a) {
362+ found = true;
363+ break;
364+ }
365+ }
366+ matched = found;
367+ }
368+
369+ if (!matched) {
370+ // Return out of the function
371+ return false;
372+ }
373 }
374
375- bool matched = false;
376+ // The rest of the logic assumes the other selectors do not match until proven otherwise
377+ matched = false;
378+
379+ if (symbol == '\0' || node->parent != nullptr) {
380+ return false;
381+ }
382+
383 if (symbol == '>') {
384 // We are just testing the parent node here
385 matched = testSelector(node->parent, trimmed);
386 } else if (symbol == '+') {
387- auto children = node->parent.children;
388+ const auto& children = node->parent->children;
389 if (children.size() > 1) {
390 for (size_t i = 0; i < children.size(); i++) {
391 Node* child_ptr = children[i].get();
392@@ -529,7 +591,7 @@ bool testSelector(Node* node, std::string selector) {
393 }
394 }
395 } else if (symbol == '~') {
396- auto children = node->parent.children;
397+ const auto& children = node->parent->children;
398 if (children.size() > 1) {
399 for (size_t i = 0; i < children.size(); i++) {
400 Node* child_ptr = children[i].get();
401@@ -548,13 +610,13 @@ bool testSelector(Node* node, std::string selector) {
402 }
403 } else if (symbol == ' ') {
404 // For the descendant combinator we have to check every parent until we match or nullptr out
405- Node* current = node->parent.get();
406+ Node* current = node->parent;
407 while (current != nullptr) {
408 if (testSelector(current, trimmed)) {
409 matched = true;
410 break;
411 }
412- current = current->parent.get();
413+ current = current->parent;
414 }
415 }
416