home readme diff tree note docs

0commit ca8775193963626ccb0e764d31f73168235c0661
1Author: Mason Wright <mason@lakefox.net>
2Date:   Sat Aug 30 17:29:24 2025 -0600
3
4    Added & nesting
5
6diff --git a/Makefile b/Makefile
7index 19982c1..d564b5d 100755
8--- a/Makefile
9+++ b/Makefile
10@@ -54,7 +54,21 @@ MD_FILES := $(shell find docs/content -name "*.md")
11 HTML_FILES := $(MD_FILES:docs/content/%.md=docs/dist/%.html)
12 
13 docs: $(HTML_FILES)
14-	@printf "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\"><channel><title>Grim Git Feed</title><link>https://grimui.com</link><description>A feed of recent commits.</description><language>en-us</language><pubDate>$$(date -R)</pubDate>$$(git log --pretty=format:'<item><title>%s</title><guid isPermaLink="false">%H</guid><author>%ae (%an)</author><pubDate>%aI</pubDate><description><![CDATA[<p><strong>Author:</strong> %an</p><p><strong>Commit:</strong> %H</p><p>%b</p><p style="white-space: pre;">%N</p>]]></description></item>' --date=iso)</channel>\n</rss>" > docs/dist/rss.xml
15+	@printf "\
16+<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
17+<rss version=\"2.0\">\n\
18+  <channel>\n\
19+    <title>Grim Git Feed</title>\n\
20+    <link>https://grimui.com</link>\n\
21+    <description>A feed of recent commits.</description>\n\
22+    <language>en-us</language>\n\
23+    <pubDate>" > docs/dist/rss.xml
24+	date -R >> docs/dist/rss.xml
25+	@printf "</pubDate>\n" >> docs/dist/rss.xml
26+	git log --pretty=format:'<item><title>%s</title><guid isPermaLink="false">%H</guid><author>%ae (%an)</author><pubDate>%aI</pubDate><description><![CDATA[<p><strong>Author:</strong> %an</p><p><strong>Commit:</strong> %H</p><p>%b</p><p style="white-space: pre;">%N</p>]]></description></item>' --date=iso >> docs/dist/rss.xml
27+	@printf "\
28+  </channel>\n\
29+</rss>" >> docs/dist/rss.xml
30 
31 # See 080f729 note for the template command
32 docs/dist/%.html: docs/content/%.md docs/components/navbar.html
33diff --git a/docs/components/navbar.html b/docs/components/navbar.html
34index 0244ad3..daf8399 100755
35--- a/docs/components/navbar.html
36+++ b/docs/components/navbar.html
37@@ -21,4 +21,12 @@
38 			<li><a href="/features/supported-dom-apis.html">DOM APIs</a></li>
39 		</ul>
40 	</details>
41+	<details open>
42+		<summary>About</summary>
43+		<ul>
44+			<li><a href="/about/general.html">General</a></li>
45+			<li><a href="/about/parsing.html">Parsing</a></li>
46+			<li><a href="/about/events.html">Events</a></li>
47+		</ul>
48+	</details>
49 </aside>
50diff --git a/docs/template.html b/docs/template.html
51index a9db999..3c56f6a 100755
52--- a/docs/template.html
53+++ b/docs/template.html
54@@ -3,7 +3,8 @@
55 	<head>
56 		<meta charset="UTF-8" />
57 		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
58-		<title>GrimUI</title>
59+		<title>Grim</title>
60+		<meta name="description" content="Grim is a new implementation of HTML/CSS/DOM API.">
61 		<style>
62 body {
63 	margin-bottom: 100px;
64diff --git a/include/grim.h b/include/grim.h
65index 205ef5c..68b7270 100755
66--- a/include/grim.h
67+++ b/include/grim.h
68@@ -214,6 +214,7 @@ class StyleHandler {
69 		Window* window;
70 
71 	public:
72+		size_t index = 0;
73 		StyleHandler(Window* w): window(w) {}
74 		// Rules from the master.css file are direct styles of this object
75 		// all sheets are parsed as children of the ROOT
76diff --git a/src/grim.cc b/src/grim.cc
77index 75aa4d3..f356404 100755
78--- a/src/grim.cc
79+++ b/src/grim.cc
80@@ -713,28 +713,29 @@ void Sheet::add(std::string selector, std::unordered_map<std::string, std::strin
81 	}
82 }
83 */
84-
85 /*
86-std::unordered_map<std::string, std::string> StyleHandler::getStyles(Node* node) {
87-	// TODO: Will need to change getStyles to first consider if the style sheet applies
88-	// then to check the selectors
89-	std::vector<std::string> parts = parseNodeParts(node);
90-
91+void getstyle(Node* node, std::vector<std::vector<std::string>> parts, Sheet* sheet, std::unordered_map<std::string, std::string>* styles) {
92 	std::vector<size_t> canidates;
93 
94-	for (auto p : parts) {
95-		if (auto sty = basemap.find(p); sty != basemap.end()) {
96-			for (auto s : sty->second) {
97-				bool found = false;
98-				// This makes sure we aren't adding the same styles to the candidates list
99-				for (auto c : canidates) {
100-					if (c == s) {
101-						found = true;
102-						break;
103+	// Now we have to start from the window.CSS.root, use the basemap to find styles
104+	// | > include a search for "*"
105+	// | test each selector, if a selector matches, go into its children and repeat
106+
107+	for (std::vector<std::string> pt : parts) {
108+		for (std::string p : pt) { 
109+			if (auto sty = sheet->basemap.find(p); sty != sheet->basemap.end()) {
110+				for (auto s : sty->second) {
111+					bool found = false;
112+					// This makes sure we aren't adding the same styles to the candidates list
113+					for (auto c : canidates) {
114+						if (c == s) {
115+							found = true;
116+							break;
117+						}
118+					}
119+					if (!found) {
120+						canidates.push_back(s);
121 					}
122-				}
123-				if (!found) {
124-					canidates.push_back(s);
125 				}
126 			}
127 		}
128@@ -743,36 +744,42 @@ std::unordered_map<std::string, std::string> StyleHandler::getStyles(Node* node)
129 	// Now we have the list of candidates next we will refined our selection
130 	// we need to pull the selectors from each candidate then run testSelector on it
131 
132-	Style* matchingStyles = new Style[canidates.size()];
133-	int index = 0;
134-
135 	for (size_t c : canidates) {
136-		// Get the possible style data
137-		Style style = styles[c];
138-		// Run testSelector with the preparsed parts
139-		if (testSelector(node, style.selector)) {
140-			matchingStyles[index] = style;
141-			index++;
142+		// need to check the type of sheet it is
143+		SheetType t = sheet->type;
144+		std::vector<std::vector<std::string>> selector = sheet->children[c].selector;
145+
146+		if (t == SheetType::STYLE) {
147+			// Run testSelector with the preparsed parts
148+			if (testSelector(node, selector)) {
149+				std::unordered_map<std::string, std::string> props = sheet->children[c].properties;
150+				for (auto p : props) {
151+					styles[p.first] = p.second;
152+				}
153+
154+				// If the Sheet matches then we will repeat the same process with that sheet
155+				if (sheet->children[c].children.size() > 0) {
156+					getstyle(parts, sheet->children[c], styles);
157+				}
158+			}
159+		} else if (t == SheetType::CONTAINER) {
160+
161 		}
162 	}
163+}
164 
165-	// matchingStyles contains the filtered canidates. The next step is to compute 
166-	// the cascade. the higher the index the later it's applied
167+// TODO:Add variable subsutution and inheritance, fill in the & in nested
168+std::unordered_map<std::string, std::string> StyleHandler::getStyles(Node* node) {
169+	// TODO: Will need to change getStyles to first consider if the style sheet applies
170+	// then to check the selectors
171+	std::vector<std::string> parts = parseNodeParts(node);
172 
173-	// Bubble sort the styles (not using quick sort because the
174-	// matching style list should be pretty small)		
175-	for (int i = 0; i < index-1; i++) {
176-		for (int j = 0; j < index-i-1; j++) {
177-			if (matchingStyles[j].index > matchingStyles[j+1].index) {
178-				std::swap(matchingStyles[j], matchingStyles[j+1]);
179-			}
180-		}
181+	std::unordered_map<std::string, std::string> styles;
182 
183-	}
184+	getstyles(node, parts, window.CSS.root, &styles);
185 
186 	// Now we have all styles except the inline styles and the inherited styles
187 
188-	delete[] matchingStyles;
189 }
190 
191 Style StyleHandler::item(int index) {
192@@ -783,6 +790,12 @@ Style StyleHandler::item(int index) {
193 // Used for root node without a parent
194 const std::vector<std::unique_ptr<Node>> EMPTY_NODE_CHILDREN_VECTOR;
195 
196+/*
197+add: & subs
198+add: testing from inheritance 
199+at-rules need to be calculated at getstyles
200+make a function to testAtRules
201+*/
202 // node: The node you are checking the selector against
203 // selector: output of parseSelector
204 bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
205@@ -1092,10 +1105,10 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
206 // |(d678a) Nth child selection                           |
207 // +------------------------------------------------------+
208 // This will match on:
209-// + nth-child()
210-// + nth-last-child()
211-// + nth-last-of_type()
212-// + nth-of-type()
213+// | nth-child()
214+// | nth-last-child()
215+// | nth-last-of_type()
216+// | nth-of-type()
217 // Instead of moving these up a layer I'm putting them 
218 // here because most selectors will not be "nth-" so why 
219 // check against every case
220diff --git a/src/parser.cc b/src/parser.cc
221index f819dcb..f83d09f 100755
222--- a/src/parser.cc
223+++ b/src/parser.cc
224@@ -311,7 +311,7 @@ std::unique_ptr<Node> parseHTML(std::istream& inputStream) {
225 			}
226 
227 			// TODO: Add a vector that stores inner text and when elements start to close out pop them but add all to their innerText
228-			// + Will need something simular for innerHTML
229+			// | Will need something simular for innerHTML
230 			if (hasText) {
231 				auto node = currentNode->createElement("text");
232 				node->setAttribute("innerText", token);
233@@ -363,11 +363,62 @@ void StyleHandler::parseStream(std::istream& inputStream) {
234 			continue;
235 		}
236 
237-
238 		if (ch == '{') {
239+			for (size_t i = buffer.length()-1; i >= 0; i--) {
240+				if (std::isspace(buffer[i])) {
241+					buffer.pop_back();
242+				} else {
243+					break;
244+				}
245+			}
246+
247 			// Parse the selector into its parts
248 			std::vector<std::vector<std::string>> parts = parseSelectorParts(buffer);
249 
250+			if (current->selector.size() > 0) {
251+				// To be able to handle a subsutution with a parent that has h1, h2
252+				// we need to rebuild the selector so we can add new copies
253+				std::vector<std::vector<std::string>> subd;
254+
255+				// Look for any & if its a style and replace it with the parsed parent selector
256+				for (size_t i = 0; i < parts.size(); i++) {
257+					bool hasAmp = false;
258+					for (size_t e = 0; e < parts[i].size(); e++) {
259+						if (parts[i][e] == "&") {
260+							hasAmp = true;
261+							break;
262+						}
263+					}
264+
265+					if (hasAmp) {
266+						// If it contains a amp then we need replace the amp with each parent selector
267+						// Inject each part of the parent
268+						// Parent [[h1],[h2]]
269+						// Child: [[& .class, &, &]]
270+						// Result: [[h1 .class, h1, h1]]
271+						// 	   [[h2 .class, h2, h2]]
272+						for (size_t e = 0; e < current->selector.size(); e++) {
273+							std::vector<std::string> temp;
274+							for (size_t f = 0; f < parts[i].size(); f++) {
275+								if (parts[i][f] == "&") {
276+									for (auto p : current->selector[e]) {
277+										temp.push_back(p);
278+									}
279+								} else {
280+									temp.push_back(parts[i][f]);
281+								}
282+							}
283+							subd.push_back(temp);
284+						}
285+					} else {
286+						// If theres nothing to replace just add to back
287+						subd.push_back(parts[i]);
288+					}
289+				}
290+
291+				parts = subd;
292+			}
293+
294 			// Find who the parent is
295 			Sheet newSheet;
296 			newSheet.parent = current;
297@@ -437,15 +488,28 @@ void StyleHandler::parseStream(std::istream& inputStream) {
298 				} else if (parts[0][0] == "@starting-style") {
299 					current->type = SheetType::STARTINGSTYLE;
300 				}
301-				// ISSUE: need to remove the leading at-rule and other data
302 			} else {
303-				// Rework this, should make a Style not a Sheet  
304 				current->type = SheetType::STYLE;
305+
306+
307 			}
308 			
309 
310 			buffer.clear();
311 		} else if (ch == '}') {
312+			// Calculate the basemap (more info in grim.h)
313+			std::vector<std::string> targetParts = current->selector[current->selector.size()-1];
314+			for (auto p : targetParts) {
315+				if (current->parent->basemap.find(p) != current->parent->basemap.end()) {
316+					current->parent->basemap[p].push_back(window->CSS.index);
317+				} else {
318+					std::vector<size_t> bm = {window->CSS.index};
319+					current->parent->basemap[p] = bm;
320+				}
321+			}
322+
323+			window->CSS.index++;
324+
325 			if (current->parent != nullptr) {
326 				current = current->parent;
327 			}
328@@ -495,7 +559,11 @@ void StyleHandler::parseStream(std::istream& inputStream) {
329 					}
330 				}
331 
332-				current->properties[key] = value;
333+				if (key.length() > 2 && key[0] == '-' && key[1] == '-') {
334+					current->variables[key] = value;
335+				} else {
336+					current->properties[key] = value;
337+				}
338 			}
339 			buffer.clear();
340 		} else {
341diff --git a/tests/css_parser.cc b/tests/css_parser.cc
342index 66bea3c..4f150fe 100755
343--- a/tests/css_parser.cc
344+++ b/tests/css_parser.cc
345@@ -47,32 +47,32 @@ void trimSpace2(std::string& str) {
346         str.erase(0, first_non_space);
347     }
348 }
349-/*
350-void compareStyle(std::string selector, std::unordered_map<std::string, std::string> props, Style style) {
351+
352+void compareStyle(std::string selector, std::unordered_map<std::string, std::string> props, Sheet sheet, SheetType type) {
353 	std::vector<std::vector<std::string>> parts = parseSelectorParts(selector);
354-	INFO(selector); 
355-	INFO(printSelector( style.selector));
356-	REQUIRE(parts.size() == style.selector.size());
357+	INFO("Expected: \"" + selector+"\""); 
358+	INFO("Parsed: \""+ printSelector(sheet.selector)+"\"");
359+	REQUIRE(parts.size() == sheet.selector.size());
360+	REQUIRE(type == sheet.type);
361 
362 	for (size_t a = 0; a < parts.size(); a++) {
363-		
364-		REQUIRE(parts[a].size() == style.selector[a].size());
365+		REQUIRE(parts[a].size() == sheet.selector[a].size());
366 
367 		for (size_t b = 0; b < parts[a].size(); b++) {
368-			REQUIRE(parts[a][b] == style.selector[a][b]);
369+			REQUIRE(parts[a][b] == sheet.selector[a][b]);
370 		}
371 	}
372 
373 	for (auto p : props) {
374 		std::string f = p.second;
375-		std::string s = style.properties[p.first];
376+		std::string s = sheet.properties[p.first];
377 		trimSpace2(f);
378 		trimSpace2(s);
379 
380 		REQUIRE(f == s);
381 	}
382 }
383-*/
384+
385 void compareInlineStyles(std::string style, std::unordered_map<std::string, std::string> targetProps) {
386 	std::unordered_map<std::string, std::string> props = parseCSSInline(style);
387 	
388@@ -155,64 +155,81 @@ html#nested {
389 
390 	StyleHandler handler = window.CSS;
391 
392-	std::cout << handler.root.children.size() << std::endl;
393-
394-	/*SECTION("StyleHandler Benchmark") {
395-		BENCHMARK("master.css") {
396-			std::ifstream tempFile("./tests/master.css");
397-			return parseCSS(tempFile);
398-		};
399-	}*/
400-
401-/*
402 	SECTION("parseCSS can parse simple CSS rules") {
403 		compareStyle("h1", {
404 				{"color", "red"}
405-				}, handler.root.styles[0]);
406+				}, handler.root.children[0],
407+				SheetType::STYLE
408+				);
409 	}
410-*/
411-/*
412+
413+
414 	SECTION("parseCSS can parse a CSS rule with a child combinator") {
415 		compareStyle("h1 > #id", {
416 				{"background", "purple"}
417-				}, 1, handler->item(1));
418+				}, handler.root.children[1],
419+				SheetType::STYLE
420+				);
421 	}
422 
423 	SECTION("parseCSS can parse a CSS rule with a pseudo class") {
424 		compareStyle("div:checked", {
425 				{"display", "flex"},
426 				{"margin", "1px 100% 20vh"}
427-				}, 2, handler->item(2));
428+				}, handler.root.children[2],
429+				SheetType::STYLE
430+			);
431+	}
432+
433+	SECTION("parseCSS can parse a the parent of a nested CSS rule") {
434+		compareStyle("html#nested", {
435+				{"background", "url(\"https://example.com\")"},
436+				}, handler.root.children[3],
437+				SheetType::STYLE
438+			);
439 	}
440 
441 	SECTION("parseCSS can parse a nested CSS rule without a &") {
442-		compareStyle("html#nested h1[value=\"nested\"]", {
443+		// "html#nested h1[value=\"nested\"]"
444+		// Child of html#nested
445+		compareStyle("h1[value=\"nested\"]", {
446 				{"color", "red"},
447-				}, 3, handler->item(3));
448+				}, handler.root.children[3].children[0],
449+				SheetType::STYLE
450+			);
451 	}
452 
453 	SECTION("parseCSS can parse a nested CSS rule with a &") {
454+		// TODO: With nested &, they will have to be computed at testSelector
455+		// | but the tree structure still works because the parent selector must still match
456 		compareStyle("html#nested.class", {
457 				{"border", "none"},
458-				}, 4, handler->item(4));
459+				}, handler.root.children[3].children[1],
460+				SheetType::STYLE
461+			);
462 	}
463 
464-	SECTION("parseCSS can parse a the parent of a nested CSS rule") {
465-		compareStyle("html#nested", {
466-				{"background", "url(\"https://example.com\")"},
467-				}, 5, handler->item(5));
468+	SECTION("parseCSS can parse a parent CSS rule with a property in between sub-classes") {
469+		compareStyle(".notice", {
470+				{"width", "90%"},
471+				{"justify-content", "center"},
472+				{"border-radius", "1rem"},
473+				{"border", "black solid 2px"},
474+				{"background-color", "#ffc107"},
475+				{"color", "black"},
476+				{"padding", "1rem"},
477+				{"font-size", "10px"}
478+				  }, handler.root.children[4],
479+					SheetType::STYLE
480+				  );
481 	}
482 
483 	SECTION("parseCSS can parse a nested CSS rule with a pseudo element") {
484-		compareStyle(".notice .notice-heading::before", {
485+		compareStyle(".notice-heading::before", {
486 				{"content", "\"i \""},
487-				}, 6, handler->item(6));
488-	}
489-
490-	SECTION("parseCSS can parse a double nested CSS rule with a pseudo element and &") {
491-		compareStyle(".notice.warning .warning-heading::before", {
492-				{"content", "\"! \""},
493-				}, 7, handler->item(7));
494+				}, handler.root.children[4].children[0],
495+				SheetType::STYLE
496+			);
497 	}
498 
499 	SECTION("parseCSS can parse a nested CSS rule with a &") {
500@@ -220,25 +237,27 @@ html#nested {
501 				{"background-color", "#d81b60"},
502 				{"border-color", "#d81b60"},
503 				{"color", "white"},
504-				}, 8, handler->item(8));
505+				}, handler.root.children[4].children[1],
506+				SheetType::STYLE
507+			);
508 	}
509 
510-	SECTION("parseCSS can parse a parent CSS rule with a property in between sub-classes") {
511-		compareStyle(".notice", {
512-				{"width", "90%"},
513-				{"justify-content", "center"},
514-				{"border-radius", "1rem"},
515-				{"border", "black solid 2px"},
516-				{"background-color", "#ffc107"},
517-				{"color", "black"},
518-				{"padding", "1rem"},
519-				{"font-size", "10px"}
520-				  }, 9, handler->item(9));
521-	}
522-*/
523+
524+
525 }
526 
527 /*
528+
529+TODO:
530+This is redundant because a correct test of this would be:
531+div, class1
532+then
533+.class2
534+then
535+.class3
536+
537+This has been tested above, will need a test on testSelector
538+
539 TEST_CASE("parseCSS can parse multiple selectors nested") {
540 	 std::string cssContent = R"~~~(
541 div, .class1 {
542@@ -253,11 +272,14 @@ div, .class1 {
543 
544 	std::istringstream inputFile(cssContent);	
545 
546-	std::unique_ptr<StyleHandler> handler = parseCSS(inputFile);
547+	Window window;
548+	window.CSS.parseStream(inputFile);
549+
550+	StyleHandler handler = window.CSS;
551 	SECTION("multi selector test") {
552 		compareStyle("div .class2", {
553 				{"background", "red"}
554-				  }, 0, handler->item(0));
555+				  }, handler->item(0));
556 		compareStyle(".class1 .class2", {
557 				{"background", "red"}
558 				  }, 1, handler->item(1));
559@@ -272,7 +294,6 @@ div, .class1 {
560 }
561 */
562 
563-
564 TEST_CASE("parseCSSInline can parse inline styles") {
565 	SECTION("parseCSSInline can parse a single property with a ending ;") {
566 		std::string inlineStyles = "background: yellow;";
567@@ -305,29 +326,3 @@ TEST_CASE("parseCSSInline can parse inline styles") {
568 				});
569 	}
570 }
571-
572-/*
573-TEST_CASE("parseCSS-at-rules") {
574-	std::string cssContent = R"~~~(
575-@container (width > 400px) {
576-  h2 {
577-    font-size: 1.5em;
578-  }
579-}
580-
581-// With an optional <container-name>
582-@container tall (height > 30rem) {
583-  p {
584-    line-height: 1.6;
585-  }
586-}
587-)~~~";
588-
589-	std::istringstream inputFile(cssContent);	
590-
591-	std::unique_ptr<StyleHandler> handler = parseCSS(inputFile);
592-	SECTION("parseCSS can parse @container") {
593-			REQUIRE(handler->item(1).name == "tall");
594-	}
595-}
596-*/