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