home readme diff tree note docs

0commit 066b887b8538733325cc07cbacfa698d05185490
1Author: Mason Wright <mason@lakefox.net>
2Date:   Sun Jul 27 21:57:10 2025 -0600
3
4    fix nested selector parsing
5
6diff --git a/src/parser.cc b/src/parser.cc
7index 73abe5f..c85915a 100644
8--- a/src/parser.cc
9+++ b/src/parser.cc
10@@ -340,6 +340,77 @@ struct SelectorAndProps {
11 	std::unordered_map<std::string, std::string> props;
12 };
13 
14+std::vector<std::string> genSelectorStrings(std::string prefix, const std::vector<SelectorAndProps>& stack) {
15+	std::vector<std::string> selectors;
16+
17+	std::string buffer;
18+	bool inParens = false;
19+	size_t len = stack[0].selector.length();
20+	for (size_t i = 0; i < len; i++) {
21+		char c = stack[0].selector[i];
22+		if (c == '(') {
23+			inParens = true;
24+		}
25+		if (c == ')') {
26+			inParens = false;
27+		}
28+		if ((c == ',' && !inParens) || i == len-1) {
29+			if (i == len-1 && c != ',') {
30+				buffer.push_back(c);
31+			}
32+			std::vector<std::string> s;
33+
34+			if (stack.size() > 1) {
35+				std::vector<SelectorAndProps> temp(stack.begin()+1, stack.end());
36+				size_t lastChar = buffer.length()-1;
37+				for (size_t k = buffer.length()-1; k >= 0; k--) {
38+					if (!std::isspace(buffer[k])) {
39+						lastChar = k+1;
40+						break;
41+					}
42+				}
43+				s = genSelectorStrings(buffer.substr(0, lastChar), temp);
44+			} else {
45+				s.push_back(buffer);
46+			}
47+
48+			for (size_t j = 0; j < s.size(); j++) {
49+				std::string temp;
50+				bool hasAmp = false;
51+				for (size_t k = 0; k < s[j].length(); k++) {
52+					char c = s[j][k];
53+					if (c == '&') {
54+						hasAmp = true;
55+						for (auto p : prefix) {
56+							temp.push_back(p);
57+						}
58+					} else {
59+						temp.push_back(c);
60+					}
61+				}
62+				// Remove trailing space
63+				size_t lastChar = temp.length() - 1;
64+				for (size_t k = temp.length()-1; k >= 0; k--) {
65+					if (!std::isspace(temp[k])) {
66+						lastChar = k+1;
67+						break;
68+					}
69+				}
70+				if (!hasAmp) {
71+					selectors.push_back(prefix+" "+temp.substr(0,lastChar));
72+				} else {
73+					selectors.push_back(temp.substr(0,lastChar));
74+				}
75+			}
76+			buffer.clear();
77+
78+		} else {
79+			buffer.push_back(c);
80+		}
81+	}
82+	return selectors;
83+}
84+
85 std::unique_ptr<StyleHandler> parseCSS(std::istream& inputStream) {
86 	// nowhitespace: value(can have whitespace); = property
87 	// a selector name is anything before a { up to a ; or a }
88@@ -371,14 +442,6 @@ std::unique_ptr<StyleHandler> parseCSS(std::istream& inputStream) {
89 			// start of a group of properties
90 			// need to parse the selector and add it to the stack
91 			current.selector = buffer;
92-
93-
94-
95-// Probally split the selector by commas here
96-// but the stack isn't setup to handle multiple selectors on the same level
97-
98-
99-
100 			stack.push_back(current);
101 			current = SelectorAndProps();
102 			
103@@ -386,45 +449,11 @@ std::unique_ptr<StyleHandler> parseCSS(std::istream& inputStream) {
104 		} else if (ch == '}') {
105 			// compile the selector and add to the handler
106                         // This handles the nested selectors
107-                        // if the first non whitespace charactor is a &
108-                        // then join them without a space else join them with
109-                        std::string selector;
110-                        for (size_t i = 0; i < stack.size(); i++) {
111-                                bool firstChar = false;
112-
113-// A better way to simplify this is to think about 
114-// generating all possible combinations
115-// if .selector == vector<string> then selectorStore = vector<string>
116-// build stack of selectors increamentally 
117-// when new branch is found >>duplicate stack<<
118-//
119-// also this only handles one branch so div > span etc not multi level
120-
121-				std::cout << ">" << stack[i].selector << std::endl;
122-                                for (size_t j = 0; j < stack[i].selector.length(); j++) {
123-                                        char c = stack[i].selector[j];
124-                                        if (!std::isspace(c) && !firstChar) {
125-                                                if (c != '&') {
126-                                                        selector.push_back(' ');
127-                                                        selector.push_back(c);
128-                                                }
129-                                                
130-                                                firstChar = true;
131-                                        } else if (firstChar) {
132-                                                if (std::isspace(c)) { 
133-                                                        if (j < stack[i].selector.length()-1 && !std::isspace(stack[i].selector[j+1])) {
134-                                                                selector.push_back(c);
135-                                                        }
136-                                                } else {
137-                                                        selector.push_back(c);
138-                                                }
139-                                        }
140-                                }
141-                        }
142+			std::vector<std::string> selectors = genSelectorStrings("", stack);
143 
144-
145-			std::cout << "-" << selector << std::endl;
146-                        handler->add(selector, current.props);
147+			for (auto s : selectors) {
148+				handler->add(s, current.props);
149+			}
150 
151                         if (!stack.empty()) {
152                                 current = stack.back();
153@@ -506,3 +535,5 @@ std::unordered_map<std::string, std::string> parseCSSInline(std::string inlineSt
154 
155 	return props;
156 }
157+
158+
159diff --git a/tests/css_parser.cc b/tests/css_parser.cc
160index ea8473d..1913211 100644
161--- a/tests/css_parser.cc
162+++ b/tests/css_parser.cc
163@@ -4,13 +4,14 @@
164 #include <iostream>
165 #include <fstream>
166 
167-void printSelector(std::vector<std::vector<std::string>> selector) {
168+std::string printSelector(std::vector<std::vector<std::string>> selector) {
169+	std::string out;
170 	for (size_t a = 0; a < selector.size(); a++) {
171 		for (size_t b = 0; b < selector[a].size(); b++) {
172-			std::cout << selector[a][b];
173+			out += selector[a][b];
174 		}
175 	}
176-	std::cout << std::endl;
177+	return out;
178 }
179 
180 void trimSpace2(std::string& str) {
181@@ -50,6 +51,8 @@ void trimSpace2(std::string& str) {
182 void compareStyle(std::string selector, std::unordered_map<std::string, std::string> props, size_t index, Style style) {
183 	REQUIRE(style.index == index);
184 	std::vector<std::vector<std::string>> parts = parseSelectorParts(selector);
185+	INFO(selector); 
186+	INFO(printSelector( style.selector));
187 	REQUIRE(parts.size() == style.selector.size());
188 
189 	for (size_t a = 0; a < parts.size(); a++) {
190@@ -231,12 +234,6 @@ html#nested {
191 				{"font-size", "10px"}
192 				  }, 9, handler->item(9));
193 	}
194-/*
195-	printSelector(handler->item(10).selector);
196-	printSelector(handler->item(11).selector);
197-	printSelector(handler->item(12).selector);
198-	printSelector(handler->item(13).selector);
199-*/
200 }
201 
202 TEST_CASE("parseCSS can parse multiple selectors nested") {
203@@ -245,7 +242,7 @@ div, .class1 {
204 	.class2 {
205 		background: red;
206 	}
207-	& .class3 {
208+	&.class3 {
209 		background: orange;
210 	}
211 }
212@@ -255,14 +252,14 @@ div, .class1 {
213 
214 	std::unique_ptr<StyleHandler> handler = parseCSS(inputFile);
215 	SECTION("multi selector test") {
216-		compareStyle(".div .class2", {
217+		compareStyle("div .class2", {
218 				{"background", "red"}
219 				  }, 0, handler->item(0));
220-		compareStyle(".div.class3", {
221-				{"background", "orange"}
222-				  }, 1, handler->item(1));
223 		compareStyle(".class1 .class2", {
224 				{"background", "red"}
225+				  }, 1, handler->item(1));
226+		compareStyle("div.class3", {
227+				{"background", "orange"}
228 				  }, 2, handler->item(2));
229 		compareStyle(".class1.class3", {
230 				{"background", "orange"}