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-*/