home readme diff tree note docs

0commit e4e05418a640eaed08cd1ec7cd8644eb1dbcca50
1Author: Mason Wright <mason@lakefox.net>
2Date:   Sun Jul 6 16:46:32 2025 -0600
3
4    Allowed parseSelectorParts to parse +p
5
6diff --git a/include/parser.h b/include/parser.h
7index 68f0246..155b798 100644
8--- a/include/parser.h
9+++ b/include/parser.h
10@@ -6,11 +6,12 @@
11 #include <istream>
12 #include <unordered_map>
13 #include <string>
14+#include <string_view>
15 
16 class Node;
17 
18 // Declare the parsing functions
19-std::unordered_map<std::string, std::string> parseAttributes(std::string token);
20+std::unordered_map<std::string, std::string> parseAttributes(std::string_view& token);
21 std::unique_ptr<Node> parseStream(std::istream& inputStream);
22 
23 #endif
24diff --git a/src/grim.cc b/src/grim.cc
25index 2bf0c39..1bf4374 100755
26--- a/src/grim.cc
27+++ b/src/grim.cc
28@@ -340,6 +340,7 @@ struct Style {
29 // and comparing a node to a selector in testSelector. This is a higher level function
30 // that should be ran once perselector and the results saved somewhere
31 // selector: any CSS selector
32+// //!TODO: have parseSelectorParts return string_views
33 std::vector<std::vector<std::string>> parseSelectorParts(std::string_view selector) {
34 	// need to account for selectors with parenthesis like: h1:(+ input:required)
35 	// need to convert all single quotes to double quotes
36@@ -377,7 +378,7 @@ std::vector<std::vector<std::string>> parseSelectorParts(std::string_view select
37 			}
38 		}
39 
40-		if (nesting == 0 && !current.empty()) {
41+		if (nesting == 0 ) {
42 			if (s == ':' || s == '[' || s == ',' || s == '#' || s == '.') {
43 				// We convert any : selectors (like :checked) to [checked]
44 				// because we store every thing as attributes on the Node
45@@ -396,32 +397,38 @@ std::vector<std::vector<std::string>> parseSelectorParts(std::string_view select
46 				}
47 				current = s;
48 			} else 
49-			if (s == '>' || s == '+' || s == '~' || s == ' ') {
50-				if (std::isspace(current.front()) || std::isspace(current.back())) {
51-					trimSpace(current);
52-				}
53-				
54-				if (current[0] == ':' && current.back() != ')') {
55-					current[0] = '[';
56-					current.push_back(']');
57+				if (s == '>' || s == '+' || s == '~' || s == ' ') {
58+					if (std::isspace(current.front()) || std::isspace(current.back())) {
59+						trimSpace(current);
60+					}		
61+					if (!current.empty()) {
62+						if (current[0] == ':' && current.back() != ')') {
63+							current[0] = '[';
64+							current.push_back(']');
65+						}
66+
67+						buffer.push_back(std::move(current));
68+						buffer.push_back("");
69+						buffer.back() += s;
70+
71+					} else
72+					if (s != ' ') {
73+						buffer.push_back("");
74+						buffer.back() += s;
75+					}
76+					current.clear();
77+				} else if (s != '\0') {
78+					current.push_back(s);
79 				}
80-				
81-				buffer.push_back(std::move(current));
82-				buffer.push_back("");
83-				buffer.back() += s;
84-				current.erase();
85-			} else if (s != '\0') {
86-				current.push_back(s);
87-			}
88 		} else if (s != '\0') {
89 			current.push_back(s);
90 		}
91 
92 		if ((s == ',' && nesting == 0) || e == sl-1) {
93+			if (std::isspace(current.front()) || std::isspace(current.back())) {
94+				trimSpace(current);
95+			};
96 			if (!current.empty() && current != ",") {
97-				if (std::isspace(current.front()) || std::isspace(current.back())) {
98-					trimSpace(current);
99-				};
100 				if (current[0] == ':' && current.back() != ')') {
101 					current[0] = '[';
102 					current.push_back(']');
103@@ -431,7 +438,7 @@ std::vector<std::vector<std::string>> parseSelectorParts(std::string_view select
104 			}
105 			parts.push_back(std::move(buffer));
106 			buffer.clear();
107-			current.erase();
108+			current.clear();
109 		}
110 		if (s == '(' || s == '[' || s == '{') {
111 			nesting++;
112diff --git a/tests/css_selector.cc b/tests/css_selector.cc
113index 38fb7a0..ee88bda 100644
114--- a/tests/css_selector.cc
115+++ b/tests/css_selector.cc
116@@ -99,6 +99,9 @@ TEST_CASE( "CSS Selection", "[css]" ) {
117 			return parseSelectorParts("h1, h1 ~p, :last-child");
118 		};
119 	}
120+	SECTION("parseSelectorParts can parse a relative selector without another reference") {
121+		checkSelectorParts("+ p", {{"+", "p"}});
122+	}
123 }
124 
125 TEST_CASE("testSelector", "[CSS]") {
126@@ -219,6 +222,11 @@ TEST_CASE("testSelector", "[CSS]") {
127 			return testSelector(input, parseSelectorParts(":not(:is(h1, html, div))"));
128 		};
129 	}
130+	SECTION("testSelector can match a :has() selector") {
131+		auto h1 = document->children[0]->children[1]->children[0].get();
132+		REQUIRE(testSelector(h1, parseSelectorParts("h1:has(+ p)")));
133+		REQUIRE_FALSE(testSelector(h1, parseSelectorParts("h1:has(+ p:required)")));
134+	}
135 	// make for:
136 	// pseudo elements
137 	// *