home
readme
diff
tree
note
docs
0commit 36bbb7f78ec07095031aafe80cdfb9aaef7bc023
1Author: Mason Wright <mason@lakefox.net>
2Date: Mon Oct 6 21:30:05 2025 -0600
3
4 Generic Unit Parsing Function
5
6diff --git a/include/grim.h b/include/grim.h
7index 9c8162c..2322226 100755
8--- a/include/grim.h
9+++ b/include/grim.h
10@@ -157,14 +157,16 @@ enum class UnitType {
11 VW, VMAX, VMIN, VB, VI, CQW, CQH, CQI, CQB, CQMIN, CQMAX, MM, Q, IN, PC, PT,
12 // Angle
13 DEG, RAD, GRAD, TURN,
14+ // Non-unit numbers
15+ NUMBER, E, PI, INF, NEGINF, NaN,
16 // Baseline Position
17 FIRST, LAST,
18 // Box Edge
19 BOX_EDGE, CONTENT, PADDING, BORDER, MARGIN, FILL, STROKE, VIEW,
20 // Function
21- CALC,
22+ CALC, MIN,
23 // Keyword
24- PRINT, SCREEN, TOKEN, FUNC,
25+ PRINT, SCREEN, TOKEN, FUNC, OPERATOR, GROUP,
26 // Property
27 BACKGROUND_COLOR, BASELINE
28 };
29diff --git a/include/parser.h b/include/parser.h
30index e2970ba..8b71a76 100755
31--- a/include/parser.h
32+++ b/include/parser.h
33@@ -16,6 +16,6 @@ std::unordered_map<std::string, std::string> parseAttributes(std::string_view& t
34 std::unique_ptr<Node> parseHTML(std::istream& inputStream);
35 std::unique_ptr<StyleHandler> parseCSS(std::istream& inputStream);
36 std::unordered_map<std::string, std::string> parseCSSInline(std::string inlineStyles);
37-Unit getUnit(std::string, std::string);
38+std::vector<Unit> getUnit(std::string);
39
40 #endif
41diff --git a/src/parser.cc b/src/parser.cc
42index 9f2001d..3d514b6 100755
43--- a/src/parser.cc
44+++ b/src/parser.cc
45@@ -6,6 +6,7 @@
46 #include <cctype>
47 #include <algorithm>
48 #include <cmath>
49+#include <string_view>
50
51 std::unordered_map<std::string, std::string> parseAttributes(std::string_view token) {
52 std::unordered_map<std::string, std::string> attrs;
53@@ -82,7 +83,7 @@ std::unordered_map<std::string, std::string> parseAttributes(std::string_view to
54 }
55
56 for (int t = value.length()-1; t >= 0; t--) {
57- // Trim the trailing / on self closing elements if there isn't a space inbetween
58+ // Trim the trailing / on self closing elements if there isn't a space in between
59 if (!std::isspace(value[t]) && value[t] != '/') {
60 sliceEnd = t;
61 break;
62@@ -275,10 +276,10 @@ std::unique_ptr<Node> parseHTML(std::istream& inputStream) {
63 continue;
64 }
65 } else if (!escaped && current == '<') {
66- // if the next charector is a !
67+ // if the next character is a !
68 if (inputStream.peek() == '!') {
69 char a,b,c;
70- // load the next three charecters (includes !)
71+ // load the next three characters (includes !)
72 if (inputStream.get(a) && inputStream.get(b) && inputStream.get(c)) {
73 // We know a == !
74 if (b == '-' && c == '-') {
75@@ -297,7 +298,7 @@ std::unique_ptr<Node> parseHTML(std::istream& inputStream) {
76 }
77 inTag = true;
78
79- // Heres where you actually make the text node and above the real nodes
80+ // Here's where you actually make the text node and above the real nodes
81 // can also prob make the vector of tokens a single variable
82 // it really just needs to be a data string bc we know the type based on inTag
83
84@@ -311,7 +312,7 @@ std::unique_ptr<Node> parseHTML(std::istream& inputStream) {
85 }
86
87 // TODO: Add a vector that stores inner text and when elements start to close out pop them but add all to their innerText
88- // | Will need something simular for innerHTML
89+ // | Will need something similar for innerHTML
90 if (hasText) {
91 auto node = currentNode->createElement("text");
92 node->setAttribute("innerText", token);
93@@ -340,7 +341,7 @@ void typeCheck(std::string key, std::string value, Unit* property, std::vector<U
94 for (size_t i = 0; i < property->children.size(); i++) {
95 bool allowed = false;
96 for (size_t j = 0; j < allowedTypes.size(); j++) {
97- if (property->children[i]->type == allowedTypes[j]) {
98+ if (property->children[i].type == allowedTypes[j]) {
99 allowed = true;
100 break;
101 }
102@@ -348,259 +349,206 @@ void typeCheck(std::string key, std::string value, Unit* property, std::vector<U
103
104 if (!allowed) {
105 std::cerr << "Invalid value of property: " << key << " Value: " << value << std::endl;
106- exit();
107+ std::exit(10);
108 }
109 }
110 }
111
112-/*
113- | Parses CSS color strings:
114- | rgba(255 125 245 / 10)
115- | rgba(255,125,245, 0.10)
116- | rgb(255,125,245)
117- | etc..
118- | value: color string
119- | max: the value you want to wrap around
120- | if max was 255 and you had -10 then it would now
121- | be 245. It also clamps the value to the max
122-*/
123-std::vector<double> parseColor(std::string value) {
124- std::vector<double> colors;
125-
126- bool set = false;
127- int collector = 0;
128-
129- bool decimal = false;
130- int decimalLevel = 1;
131- bool negitive = false;
132-
133- for (size_t i = 0; i < value.length(); i++) {
134- int v = value[i]-'0';
135- if (v >= 0 && v <= 9) {
136- collector = (collector*10) + v;
137- set = true;
138- if (decimal) {
139- decimalLevel *= 10;
140- }
141- } else if (value[i] != '.' && set) {
142- if (negitive) {
143- collector *= -1;
144- }
145-
146- negitive = false;
147- colors.push_back(collector/decimalLevel);
148-
149- set = false;
150- decimalLevel = 1;
151- decimal = false;
152- collector = 0;
153- } else if (value[i] == '.') {
154- decimal = true;
155- } else if (value[i] == '-') {
156- negitive = true;
157- }
158- }
159-
160- if (colors.size() < 4) {
161- colors.push_back(0);
162- }
163- return colors;
164-}
165-
166-double hueToRGB(double p, double q, double t) {
167- if (t < 0) t += 1;
168- if (t > 1) t -= 1;
169- if (t < 1.0 / 6.0) return p + (q - p) * 6.0 * t;
170- if (t < 1.0 / 2.0) return q;
171- if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6.0;
172- return p;
173-}
174-
175-std::vector<Unit> getUnit(std::string key, std::string value) {
176- // NOTES:
177- // | All colors are stored as rgba in hex format
178-
179+struct SuffixParse {
180+ std::string_view suffix;
181+ UnitType type;
182+ int trim; // chars to strip from the right
183+};
184+
185+static constexpr SuffixParse table[] = {
186+ {"cqmin", UnitType::CQMIN, 5},
187+ {"cqmax", UnitType::CQMAX, 5},
188+ {"rcap", UnitType::RCAP, 4},
189+ {"vmax", UnitType::VMAX, 4},
190+ {"vmin", UnitType::VMIN, 4},
191+ {"grad", UnitType::GRAD, 4},
192+ {"turn", UnitType::TURN, 4},
193+ {"cap", UnitType::CAP, 3},
194+ {"rch", UnitType::RCH, 3},
195+ {"rem", UnitType::REM, 3},
196+ {"rex", UnitType::REX, 3},
197+ {"ric", UnitType::RIC, 3},
198+ {"rlh", UnitType::RLH, 3},
199+ {"cqw", UnitType::CQW, 3},
200+ {"cqh", UnitType::CQH, 3},
201+ {"cqi", UnitType::CQI, 3},
202+ {"cqb", UnitType::CQB, 3},
203+ {"deg", UnitType::DEG, 3},
204+ {"rad", UnitType::RAD, 3},
205+ {"px", UnitType::PX, 2},
206+ {"em", UnitType::EM, 2},
207+ {"cm", UnitType::CM, 2},
208+ {"ch", UnitType::CH, 2},
209+ {"ex", UnitType::EX, 2},
210+ {"ic", UnitType::IC, 2},
211+ {"lh", UnitType::LH, 2},
212+ {"vh", UnitType::VH, 2},
213+ {"vw", UnitType::VW, 2},
214+ {"vb", UnitType::VB, 2},
215+ {"vi", UnitType::VI, 2},
216+ {"mm", UnitType::MM, 2},
217+ {"in", UnitType::IN, 2},
218+ {"pc", UnitType::PC, 2},
219+ {"pt", UnitType::PT, 2},
220+ {"%", UnitType::PERCENT,1},
221+ {"q", UnitType::Q, 1},
222+};
223+
224+static const std::unordered_map<std::string_view, UnitType> kOp{
225+ {"+", UnitType::ADD}, {"-", UnitType::SUB}, {"*", UnitType::MULT},
226+ {"/", UnitType::DIV}, {"==",UnitType::EQ}, {">", UnitType::GT},
227+ {"<", UnitType::LT}, {">=",UnitType::GE}, {"<=",UnitType::LE},
228+ {"e", UnitType::E}, {"pi", UnitType::PI}, {"infinity", UnitType::INF},
229+ {"-infinity", UnitType::NEGINF}, {"NaN", UnitType::NaN}
230+};
231+
232+static const std::unordered_map<std::string_view, float> kAbsSize{
233+ {"xx-small",0.6f}, {"x-small",0.75f}, {"small",0.89f}, {"medium",1.0f},
234+ {"large",1.2f}, {"x-large",1.5f}, {"xx-large",2.0f}, {"xxx-large",3.0f}
235+};
236+
237+std::vector<Unit> getUnit(std::string value) {
238 std::vector<Unit> units;
239
240- std::vector<std::string> parts;
241 std::string buffer = "";
242- bool inParentheses = false;
243+ int parenDepth = 0;
244 bool inQuotes = false;
245- char quoteType = '';
246+ // Needs to not be empty but can't be a quote
247+ char quoteType = 'a';
248 bool escaped = false;
249
250 for (size_t i = 0; i < value.length(); i++) {
251 char v = value[i];
252 if (v == '\\' && !escaped) escaped = true;
253-
254- if (v == '(' && !inParentheses && !inQuotes) inParentheses = true;
255- else if (v == ')' && inParentheses && !inQuotes) inParentheses = false;
256- else if (v == '"' || v == '\'' && !inParentheses) {
257- if (inQuote && v == quoteType) {
258+ if ((v == '"' || v == '\'') && parenDepth == 0) {
259+ if (inQuotes && v == quoteType) {
260 inQuotes = false;
261- } else if (!inQuote) {
262+ } else if (!inQuotes) {
263 quoteType = v;
264 inQuotes = true;
265 }
266
267- } else if (!inQuotes && !inParentheses && v == ' ') {
268- // Split by spaces into parts
269- parts.push_back(buffer);
270- buffer.clear();
271- } else if (!inQuotes && !inParentheses && v == ',' || i >= value.length()-1) {
272- // EOL or a ','
273-
274- // add the last one
275- parts.push_back(buffer);
276- buffer.clear();
277+ }
278
279- // Main logic
280- // Loop through each part and convert it into its type
281- // the code around this point is breaking the css string
282- // that may look like this:
283- // 1px 3% 67vw 2px, rgba(123,456,789,0%)
284- // into:
285- // parts = [["1px", "3%", "67vw", "2px"],["rgba(123,456,789,0%)"]]
286- //
287- // the first for loop (j) will go through its parts and once its
288- // done it will figure out what type of collection it is
289- // in the example above it is a set of units for either margin or padding
290- // and the second one is a color value
291- // once it knows the type of collection it will add it to the units vector
292-
293- for (size_t j = 0; j < parts.size(); j++) {
294- Unit property;
295- for (size_t k = 0; k < parts[j].size(); k++) {
296- std::string part = parts[j][k];
297- UnitType ut;
298- UnitValue uv;
299-
300- if (value.starts_with("#")) {
301- ut = UnitType::HEX;
302- uv.UINT = std::stoul(value.substr(1), nullptr, 16);
303- } else if (value.starts_with("rgb")) {
304- ut = UnitType::RGBA;
305- std::vector<double> colors = parseColor(value);
306- uv.UINT = (static_cast<int>(colors[0]) << 24) | (static_cast<int>(colors[1]) << 16) | (static_cast<int>(colors[2]) << 8) | static_cast<int>(255*colors[3]/100);
307- } else if (value.starts_with("hsl")) {
308- ut = UnitType::HSL;
309- std::vector<double> colors = parseColor(value);
310- // Convert the parsed values to rgba
311- int r,g,b;
312- double h = colors[0]/360.0;
313- double s = colors[1]/100.0;
314- double l = colors[2]/100.0;
315- if (colors[1] == 0) {
316- // Gray scale
317- int lInt = l*255;
318- r = lInt;
319- g = lInt;
320- b = lInt;
321- } else {
322- double q = (l < 0.5) ? l * (1.0 + s) : l + s - (l * s);
323- double p = 2.0 * l - q;
324+ if (!inQuotes) {
325+ if (v == '(') parenDepth++;
326+ else if (v == ')') parenDepth--;
327+ }
328
329- r = static_cast<int>(hueToRGB(p, q, h + 1.0 / 3.0) * 255.0);
330- g = static_cast<int>(hueToRGB(p, q, h) * 255.0);
331- b = static_cast<int>(hueToRGB(p, q, h - 1.0 / 3.0) * 255.0);
332- }
333+ if (parenDepth > 0) {
334+ buffer.push_back(v);
335+ continue;
336+ } else if (v == ')') {
337+ // We are at the end of the parentheseses and we need to pass it for more parsing
338+ std::string name;
339+ bool nameset = false;
340+ std::string value;
341
342- // convert hsl to rgb/hex
343- uv.UINT = (r << 24) | (g << 16) | (b << 8) | static_cast<int>(255*colors[3]/100);
344- } else if (value.starts_with("hwb")) {
345- ut = UnitType::HWB;
346- std::vector<double> colors = parseColor(value);
347- // if gray scale or close
348- int r,g,b;
349- colors[1] /= 100;
350- colors[2] /= 100;
351- if (colors[1] + colors[2] > 1.0 - 1e-6) {
352- r = static_cast<int>(colors[1] * 255.0);
353- g = r;
354- b = r;
355- } else {
356- double mod = std::fmod(colors[0], 360.0);
357- if (mod < 0) mod += 360.0;
358-
359- double i = std::floor(mod / 60.0);
360- double f = (mod / 60.0) - i;
361-
362- double rp = 0.0;
363- double gp = 0.0;
364- double bp = 0.0;
365-
366- switch (static_cast<int>(i)) {
367- case 0: rp = 1.0; gp = f; bp = 0.0; break;
368- case 1: rp = 1.0 - f; gp = 1.0; bp = 0.0; break;
369- case 2: rp = 0.0; gp = 1.0; bp = f; break;
370- case 3: rp = 0.0; gp = 1.0 - f; bp = 1.0; break;
371- case 4: rp = f; gp = 0.0; bp = 1.0; break;
372- case 5: rp = 1.0; gp = 0.0; bp = 1.0 - f; break;
373- default: // Should not happen for valid H
374- rp = gp = bp = 0.0; break;
375- }
376+ for (size_t j = 0; j < buffer.size(); j++) {
377+ if (nameset) {
378+ value.push_back(buffer[j]);
379+ } else {
380+ if (buffer[j] == '(') nameset = true;
381+ else name.push_back(buffer[j]);
382+ }
383+ }
384
385- double v = 1.0 - colors[1] - colors[2];
386+ std::vector<Unit> children = getUnit(value);
387+ Unit unit;
388+ unit.type = UnitType::FUNC;
389
390- r = static_cast<int>(((rp * v) + colors[1])*255.0);
391- g = static_cast<int>(((gp * v) + colors[1])*255.0);
392- b = static_cast<int>(((bp * v) + colors[1])*255.0);
393- }
394+ if (name == "calc") {
395+ unit.value.ENUM = UnitType::CALC;
396+ } else if (name == "min") {
397+ unit.value.ENUM = UnitType::MIN;
398+ } else if (name == "") {
399+ unit.value.ENUM = UnitType::GROUP;
400+ }
401
402- uv.UINT = (r << 24) | (g << 16) | (b << 8) | static_cast<int>(255*colors[3]/100);
403- } else if (value == "xx-small" && value == "x-small" && value == "small" && value == "medium" &&
404- value == "large" && value == "x-large" && value == "xx-large" && value == "xxx-large" ) {
405- // <absolute-size> read as a EM
406- ut = UnitType::EM;
407-
408- if (value == "xx-small") uv.FLOAT = 0.6;
409- else if (value == "x-small") uv.FLOAT = 0.75;
410- else if (value == "small") uv.FLOAT = 0.89;
411- else if (value == "medium") uv.FLOAT = 1;
412- else if (value == "large") uv.FLOAT = 1.2;
413- else if (value == "x-large") uv.FLOAT = 1.5;
414- else if (value == "xx-large") uv.FLOAT = 2;
415- else if (value == "xxx-large") uv.FLOAT = 3;
416- } else if (value.ends_with("baseline")) {
417- ut = UnitType::BASELINE;
418- if (value.starts_with("first")) uv.ENUM = UnitType::FIRST;
419- else if (value.starts_with("last")) uv.ENUM = UnitType::LAST;
420- else uv.ENUM = UnitType::FIRST;
421- } else if (value.ends_with("-box")) {
422- ut = UnitType::BOX_EDGE;
423-
424- if (value.starts_with("content")) uv.ENUM = UnitType::CONTENT;
425- else if (value.starts_with("padding")) uv.ENUM = UnitType::PADDING;
426- else if (value.starts_with("border")) uv.ENUM = UnitType::BORDER;
427- else if (value.starts_with("margin")) uv.ENUM = UnitType::MARGIN;
428- else if (value.starts_with("fill")) uv.ENUM = UnitType::FILL;
429- else if (value.starts_with("stroke")) uv.ENUM = UnitType::STROKE;
430- else if (value.starts_with("view")) uv.ENUM = UnitType::VIEW;
431- }
432+ if (children.size() > 0) {
433+ unit.children = children;
434+ children.clear();
435+ }
436+
437+ units.push_back(unit);
438+ buffer.clear();
439+ } else if ((!inQuotes && (v == ',' || std::isspace(v))) || i >= value.length()-1) {
440+ if (v != ',' && !std::isspace(v)) buffer.push_back(v);
441
442- // What about cases like rotate(calc(10deg)) or even rgb should parse sub values
443+ Unit unit;
444
445- Unit unit;
446- unit.type = ut;
447- unit.value = uv;
448+ // Split by spaces into parts
449+
450+ bool found = false;
451
452- property.children.push_back(unit);
453+ for (const auto& [suffix, type, trim] : table) {
454+ if (buffer.ends_with(suffix)) {
455+ unit.value.FLOAT = std::stof(std::string(buffer.substr(0, buffer.size() - trim)));
456+ unit.type = type;
457+ found = true;
458+ break;
459 }
460+ }
461
462- // ISSUE: need to find the property type before adding it
463- // probally cut back on the super intensive parsing like the colors
464- // and move to a lightweight recursive (lol) approach
465- // the typeChecking of key values could also be moved to a different function that calls this one
466+ if (!found) {
467+ if (buffer.starts_with("#")) {
468+ unit.type = UnitType::HEX;
469+ unit.value.UINT = std::stoul(buffer.substr(1), nullptr, 16);
470+ found = true;
471+ } else if (auto it = kOp.find(buffer); it != kOp.end()) {
472+ unit.type = UnitType::OPERATOR;
473+ unit.value.ENUM = it->second;
474+ found = true;
475+ } else if (auto it = kAbsSize.find(buffer); it != kAbsSize.end()) {
476+ unit.type = UnitType::EM;
477+ unit.value.FLOAT = it->second;
478+ found = true;
479+ } else if (buffer.ends_with("baseline")) {
480+ unit.type = UnitType::BASELINE;
481+ if (buffer.starts_with("first")) unit.value.ENUM = UnitType::FIRST;
482+ else if (buffer.starts_with("last")) unit.value.ENUM = UnitType::LAST;
483+ else unit.value.ENUM = UnitType::FIRST;
484+ found = true;
485+ } else if (buffer.ends_with("-box")) {
486+ unit.type = UnitType::BOX_EDGE;
487+
488+ if (buffer.starts_with("content")) unit.value.ENUM = UnitType::CONTENT;
489+ else if (buffer.starts_with("padding")) unit.value.ENUM = UnitType::PADDING;
490+ else if (buffer.starts_with("border")) unit.value.ENUM = UnitType::BORDER;
491+ else if (buffer.starts_with("margin")) unit.value.ENUM = UnitType::MARGIN;
492+ else if (buffer.starts_with("fill")) unit.value.ENUM = UnitType::FILL;
493+ else if (buffer.starts_with("stroke")) unit.value.ENUM = UnitType::STROKE;
494+ else if (buffer.starts_with("view")) unit.value.ENUM = UnitType::VIEW;
495+ found = true;
496+ } else {
497+ // Maybe its a number?
498+ try {
499+ unit.value.FLOAT = std::stof(buffer);
500+ unit.type = UnitType::NUMBER;
501+ found = true;
502+ } catch (...) {
503+ found = false;
504+ }
505+ }
506+ }
507
508- if (key == "background-color") {
509- typeCheck(key, value, property, {UnitType::RGB, UnitType::RGBA, UnitType::HSL, UnitType::HSLA, UnitType::HWB, UnitType::HEX})
510- property.type == UnitType::BACKGROUND_COLOR;
511- }
512+ if (found) {
513+ // Only process if its found
514+ units.push_back(unit);
515
516- units.push_back(property);
517+ buffer.clear();
518+ } else if (v == ',') {
519+ // if we hit a comma before we match we can dump the
520+ // buffer and try again
521+ std::cerr << "Unable to parse buffer: " << buffer << std::endl;
522+ buffer.clear();
523 }
524
525- parts.clear();
526 } else {
527 buffer.push_back(v);
528 }
529@@ -629,7 +577,7 @@ void StyleHandler::parseStream(std::istream& inputStream) {
530 while (inputStream.get(ch)) {
531 if (ch == '*' && inputStream.peek() == '/') {
532 incomment = false;
533- // Skip ahead to the next letter so we don't caputure the trailing /
534+ // Skip ahead to the next letter so we don't capture the trailing /
535 inputStream.get(ch);
536 continue;
537 }
538@@ -654,7 +602,7 @@ void StyleHandler::parseStream(std::istream& inputStream) {
539 std::vector<std::vector<std::string>> parts = parseSelectorParts(buffer);
540
541 if (current->selector.size() > 0) {
542- // To be able to handle a subsutution with a parent that has h1, h2
543+ // To be able to handle a substitution with a parent that has h1, h2
544 // we need to rebuild the selector so we can add new copies
545 std::vector<std::vector<std::string>> subd;
546
547@@ -689,7 +637,7 @@ void StyleHandler::parseStream(std::istream& inputStream) {
548 subd.push_back(temp);
549 }
550 } else {
551- // If theres nothing to replace just add to back
552+ // If there's nothing to replace just add to back
553 subd.push_back(parts[i]);
554 }
555 }
556@@ -809,7 +757,7 @@ void StyleHandler::parseStream(std::istream& inputStream) {
557 }
558
559 } else if (parts[0][0] == "@layer") {
560- // ISSUE: need to add speficity
561+ // ISSUE: need to add specificity
562
563 }
564
565@@ -831,7 +779,7 @@ void StyleHandler::parseStream(std::istream& inputStream) {
566 firstValue = true;
567 value += c;
568 } else if (keyFound && firstValue && !(std::isspace(c) && i < buffer.length()-1 && std::isspace(buffer[i+1]))) {
569- // if the key is found and the charactor isn't a ':'
570+ // if the key is found and the character isn't a ':'
571 // also (if c is a space and the next c will be a space)
572 // this is to auto trim duplicate spaces from the value
573 value += c;
574@@ -875,7 +823,7 @@ std::unordered_map<std::string, std::string> parseCSSInline(std::string inlineSt
575 !(std::isspace(c) && i < buffer.length()-1 && std::isspace(buffer[i+1])) &&
576 c != ';'
577 ) {
578- // if the key is found and the charactor isn't a ':'
579+ // if the key is found and the character isn't a ':'
580 // also (if c is a space and the next c will be a space)
581 // this is to auto trim duplicate spaces from the value
582 value += c;
583diff --git a/src/parser.cc.bak b/src/parser.cc.bak
584new file mode 100755
585index 0000000..2578022
586--- /dev/null
587+++ b/src/parser.cc.bak
588@@ -0,0 +1,804 @@
589+#include "grim.h"
590+#include <iostream>
591+#include <fstream>
592+#include <sstream>
593+#include <unordered_map>
594+#include <cctype>
595+#include <algorithm>
596+#include <cmath>
597+#include <string_view>
598+
599+std::unordered_map<std::string, std::string> parseAttributes(std::string_view token) {
600+ std::unordered_map<std::string, std::string> attrs;
601+
602+ bool inQuote = false;
603+ bool escaped = false;
604+ char quoteType = '"';
605+
606+ std::string word;
607+ for (unsigned short i = 0; i < token.length(); i++) {
608+ char current = token[i];
609+ if ((current == '"' || current == '\'') && !escaped) {
610+ if (inQuote && current == quoteType) {
611+ inQuote = false;
612+ } else if (!inQuote) {
613+ inQuote = true;
614+ quoteType = current;
615+ }
616+ }
617+
618+ if (current == '\\') { //'
619+ escaped = true;
620+ continue;
621+ }
622+
623+ if ((!std::isspace(current) || inQuote) && i != token.length()-1) {
624+ word += current;
625+ } else {
626+ if (i == token.length()-1) {
627+ word += current;
628+ }
629+
630+ std::string name = "";
631+ std::string value = "";
632+ bool setValue = false;
633+
634+ for (auto c : word) {
635+ if (c == '=') {
636+ setValue = true;
637+ continue;
638+ }
639+
640+ if (setValue) {
641+ value += c;
642+ } else {
643+ name += c;
644+ }
645+ }
646+
647+ word = "";
648+
649+ std::string trimmedName = "";
650+ for (auto c : name) {
651+ if (!std::isspace(c)) {
652+ trimmedName += c;
653+ }
654+ }
655+
656+ if (attrs.size() == 0) {
657+ value = '"'+trimmedName+'"';
658+ trimmedName = "tagName";
659+ }
660+
661+ std::string trimmedValue = "";
662+ int sliceStart = 0;
663+ int sliceEnd = value.length();
664+
665+ if (value.length() >= 2) {
666+ for (unsigned short t = 0; t < value.length(); t++) {
667+ if (!std::isspace(value[t])) {
668+ sliceStart = t;
669+ break;
670+ }
671+ }
672+
673+ for (int t = value.length()-1; t >= 0; t--) {
674+ // Trim the trailing / on self closing elements if there isn't a space inbetween
675+ if (!std::isspace(value[t]) && value[t] != '/') {
676+ sliceEnd = t;
677+ break;
678+ }
679+ }
680+
681+ // Add and subtract 1 from each side to remove quotes
682+ for (unsigned short t = sliceStart+1; t < sliceEnd; t++) {
683+ trimmedValue += value[t];
684+ }
685+ }
686+
687+ attrs[trimmedName] = trimmedValue;
688+ }
689+ }
690+
691+ return attrs;
692+}
693+
694+std::unique_ptr<Node> parseHTML(std::istream& inputStream) {
695+ std::unique_ptr<Node> root = std::make_unique<Node>();
696+ root->setTagName("root");
697+
698+ Node* currentNode = root.get();
699+
700+ bool inTag = false;
701+ bool escaped = false;
702+ bool inQuote = false;
703+ bool inComment = false;
704+ bool waitToClose = false;
705+ char quoteType = '"';
706+
707+ std::string token;
708+
709+ char current;
710+ while (inputStream.get(current)) {
711+ // Finds the --> and removes it then resets inComment
712+ if (inComment) {
713+ // added the peek to prevent hitting on every -
714+ if (current == '-' && inputStream.peek() == '-') {
715+ char a,b;
716+ // load the next two
717+ if (inputStream.get(a) && inputStream.get(b)) {
718+ // We know a == -
719+ if (b == '>') {
720+ // Close the comment
721+ inComment = false;
722+ }
723+ }
724+
725+ if (!inComment) {
726+ // we don't put anything back and that puts us where we need to be
727+ continue;
728+ } else {
729+ // Not the end
730+ inputStream.putback(b);
731+ inputStream.putback(a);
732+ }
733+ }
734+ continue;
735+ }
736+
737+ std::string tagName = currentNode->getTagName();
738+ if (!waitToClose && (
739+ tagName == "style" ||
740+ tagName == "script" ||
741+ tagName == "textarea"
742+ ) && current == '<' && inputStream.peek() == '/' ) {
743+ // If its the starting of a closing tag and we are inside of a special tag
744+ // we need to see if the upcoming closing tag is the correct closing tag
745+ // if it is then we put the read ahead back and waitToClose. This will let normal parsing
746+ // continue
747+ const size_t readTo = tagName.length()+2;
748+ std::string buffer(readTo, '\0');
749+
750+ inputStream.read(&buffer[0], readTo);
751+
752+ if (buffer == "/"+tagName+">") {
753+ waitToClose = true;
754+ } else {
755+ token += current;
756+ }
757+
758+ for (int i = readTo-1; i>= 0; i--) {
759+ inputStream.putback(buffer[i]);
760+ }
761+ if (!waitToClose) {
762+ continue;
763+ }
764+ } else if (!waitToClose && (
765+ tagName == "style" ||
766+ tagName == "script" ||
767+ tagName == "textarea"
768+ )) {
769+ token += current;
770+ continue;
771+ }
772+
773+ if (inTag) {
774+ if ((current == '"' || current == '\'') && !escaped) {
775+ if (inQuote && current == quoteType) {
776+ inQuote = false;
777+ } else if (!inQuote) {
778+ inQuote = true;
779+ quoteType = current;
780+ }
781+ }
782+
783+ if (current == '>' && !inQuote && !escaped) {
784+ inTag = false;
785+ waitToClose = false;
786+ // Even if the next tag is next still add a text tag as we can just check if its empty and remove it
787+ // this check would still have to be done either way so we aren't wasting anything
788+ if (token.length() > 0) {
789+ bool empty = true;
790+ bool closingTag = false;
791+ for (size_t i = 0; i < token.length(); i++) {
792+ auto c = token[i];
793+ if (std::isspace(c)) {
794+ continue;
795+ } else if (c == '/') {
796+ closingTag = true;
797+ } else {
798+ empty = false;
799+ break;
800+ }
801+ }
802+
803+ bool selfClosing = false;
804+ int selfClosingPosition = token.length()-1;
805+ if (!closingTag) {
806+ for (int i = token.length()-1; i >= 0; i--) {
807+ auto c = token[i];
808+ if (std::isspace(c)) {
809+ continue;
810+ } else if (c == '/') {
811+ selfClosingPosition = i;
812+ selfClosing = true;
813+ break;
814+ } else {
815+ break;
816+ }
817+ }
818+ }
819+
820+ if (!empty) {
821+ if (selfClosing) {
822+ //Create element and don't jump inside
823+ // Use node instead of currentNode because we do not need to jump into the created element
824+ auto attrs = parseAttributes(token.substr(0, selfClosingPosition));
825+ auto node = currentNode->createElement(attrs["tagName"]);
826+ for (auto const& pair : attrs) {
827+ // All attributes are stored as strings so we can just throw them in
828+ node->setAttribute(pair.first, pair.second);
829+ }
830+ } else if (closingTag) {
831+ // Checksum and tree move
832+ std::string tagName = "";
833+ for (auto t : token) {
834+ if (!std::isspace(t) && t != '/') {
835+ tagName += t;
836+ } else if (t == '/') {
837+ // For closing tags we just want the name
838+ continue;
839+ } else if (tagName.length() > 0) {
840+ break;
841+ }
842+ }
843+ if (currentNode->getTagName() == tagName) {
844+ currentNode = currentNode->parent;
845+ } else {
846+ std::cerr << "malformed html: closing tag (</" << tagName << ">) found for <" << currentNode->getTagName() << ">" << std::endl;
847+ }
848+ } else {
849+ // Create a element and jump inside
850+ auto attrs = parseAttributes(token);
851+ // Don't add elements like <!DOCTYPE>
852+ if (attrs["tagName"][0] != '!') {
853+ currentNode = currentNode->createElement(attrs["tagName"]);
854+ for (auto const& pair : attrs) {
855+ // All attributes are stored as strings so we can just throw them in
856+ currentNode->setAttribute(pair.first, pair.second);
857+ }
858+ }
859+ }
860+ }
861+ }
862+
863+ token = "";
864+ continue;
865+ }
866+ } else if (!escaped && current == '<') {
867+ // if the next charector is a !
868+ if (inputStream.peek() == '!') {
869+ char a,b,c;
870+ // load the next three charecters (includes !)
871+ if (inputStream.get(a) && inputStream.get(b) && inputStream.get(c)) {
872+ // We know a == !
873+ if (b == '-' && c == '-') {
874+ inComment = true;
875+ }
876+ }
877+
878+ if (inComment) {
879+ continue;
880+ } else {
881+ // Not a comment add all back
882+ inputStream.putback(c);
883+ inputStream.putback(b);
884+ inputStream.putback(a);
885+ }
886+ }
887+ inTag = true;
888+
889+ // Heres where you actually make the text node and above the real nodes
890+ // can also prob make the vector of tokens a single variable
891+ // it really just needs to be a data string bc we know the type based on inTag
892+
893+
894+ bool hasText = false;
895+ for (auto t : token) {
896+ if (!std::isspace(t)) {
897+ hasText = true;
898+ break;
899+ }
900+ }
901+
902+ // TODO: Add a vector that stores inner text and when elements start to close out pop them but add all to their innerText
903+ // | Will need something simular for innerHTML
904+ if (hasText) {
905+ auto node = currentNode->createElement("text");
906+ node->setAttribute("innerText", token);
907+ }
908+
909+
910+ token = "";
911+ continue;
912+ }
913+
914+ escaped = false;
915+
916+ if (current == '\\') { //'
917+ escaped = true;
918+ continue;
919+ }
920+
921+ token += current;
922+ }
923+
924+ return root;
925+}
926+
927+
928+void typeCheck(std::string key, std::string value, Unit* property, std::vector<UnitType> allowedTypes) {
929+ for (size_t i = 0; i < property->children.size(); i++) {
930+ bool allowed = false;
931+ for (size_t j = 0; j < allowedTypes.size(); j++) {
932+ if (property->children[i].type == allowedTypes[j]) {
933+ allowed = true;
934+ break;
935+ }
936+ }
937+
938+ if (!allowed) {
939+ std::cerr << "Invalid value of property: " << key << " Value: " << value << std::endl;
940+ std::exit(10);
941+ }
942+ }
943+}
944+
945+struct SuffixParse {
946+ std::string_view suffix;
947+ UnitType type;
948+ int trim; // chars to strip from the right
949+};
950+
951+static constexpr SuffixParse table[] = {
952+ {"cqmin", UnitType::CQMIN, 5},
953+ {"cqmax", UnitType::CQMAX, 5},
954+ {"rcap", UnitType::RCAP, 4},
955+ {"vmax", UnitType::VMAX, 4},
956+ {"vmin", UnitType::VMIN, 4},
957+ {"cap", UnitType::CAP, 3},
958+ {"rch", UnitType::RCH, 3},
959+ {"rem", UnitType::REM, 3},
960+ {"rex", UnitType::REX, 3},
961+ {"ric", UnitType::RIC, 3},
962+ {"rlh", UnitType::RLH, 3},
963+ {"cqw", UnitType::CQW, 3},
964+ {"cqh", UnitType::CQH, 3},
965+ {"cqi", UnitType::CQI, 3},
966+ {"cqb", UnitType::CQB, 3},
967+ {"px", UnitType::PX, 2},
968+ {"em", UnitType::EM, 2},
969+ {"cm", UnitType::CM, 2},
970+ {"ch", UnitType::CH, 2},
971+ {"ex", UnitType::EX, 2},
972+ {"ic", UnitType::IC, 2},
973+ {"lh", UnitType::LH, 2},
974+ {"vh", UnitType::VH, 2},
975+ {"vw", UnitType::VW, 2},
976+ {"vb", UnitType::VB, 2},
977+ {"vi", UnitType::VI, 2},
978+ {"mm", UnitType::MM, 2},
979+ {"in", UnitType::IN, 2},
980+ {"pc", UnitType::PC, 2},
981+ {"pt", UnitType::PT, 2},
982+ {"%", UnitType::PERCENT,1},
983+ {"q", UnitType::Q, 1},
984+};
985+
986+std::vector<Unit> getUnit(std::string value) {
987+ std::vector<Unit> units;
988+ std::vector<Unit> children;
989+
990+
991+ std::string buffer = "";
992+ std::string parenthesesesBuffer = "";
993+ bool inParentheses = false;
994+ bool inQuotes = false;
995+ // Needs to not be empty but can't be a quote
996+ char quoteType = 'a';
997+ bool escaped = false;
998+
999+ for (size_t i = 0; i < value.length(); i++) {
1000+ char v = value[i];
1001+ if (v == '\\' && !escaped) escaped = true;
1002+
1003+ if (inParentheses) {
1004+ parenthesesesBuffer.push_back(v);
1005+ }
1006+
1007+ // The way this is setup to parse func() calls is like so
1008+ // | calc(1px, 2px);
1009+ // | -> value of 1px, 2px get's put into getUnit()
1010+ // | once those are pulled into children
1011+ // | the buffer only contains calc
1012+ // | add the children to the parsed keyword
1013+
1014+
1015+ if (v == '(' && !inParentheses && !inQuotes) inParentheses = true;
1016+ else if (v == ')' && inParentheses && !inQuotes) {
1017+ children = getUnit(parenthesesesBuffer);
1018+ inParentheses = false;
1019+ parenthesesesBuffer.clear();
1020+ } else if ((v == '"' || v == '\'') && !inParentheses) {
1021+ if (inQuotes && v == quoteType) {
1022+ inQuotes = false;
1023+ } else if (!inQuotes) {
1024+ quoteType = v;
1025+ inQuotes = true;
1026+ }
1027+
1028+ } else if ((!inQuotes && !inParentheses && (v == ',' || std::isspace(v))) || i >= value.length()-1) {
1029+ if (buffer.length() == 0) continue;
1030+ if (v != ',' && !std::isspace(v)) buffer.push_back(v);
1031+
1032+ Unit unit;
1033+
1034+ // Split by spaces into parts
1035+
1036+ bool found = false;
1037+
1038+ for (const auto& [suffix, type, trim] : table) {
1039+ if (buffer.ends_with(suffix)) {
1040+ unit.value.FLOAT = std::stof(std::string(buffer.substr(0, buffer.size() - trim)));
1041+ unit.type = type;
1042+ found = true;
1043+ break;
1044+ }
1045+ }
1046+
1047+ if (!found) {
1048+ if (buffer.starts_with("#")) {
1049+ unit.type = UnitType::HEX;
1050+ unit.value.UINT = std::stoul(buffer.substr(1), nullptr, 16);
1051+ found = true;
1052+ } else if (buffer == "xx-small" || buffer == "x-small" || buffer == "small" || buffer == "medium" ||
1053+ buffer == "large" || buffer == "x-large" || buffer == "xx-large" || buffer == "xxx-large" ) {
1054+ // <absolute-size> read as a EM
1055+ unit.type = UnitType::EM;
1056+
1057+ if (buffer == "xx-small") unit.value.FLOAT = 0.6;
1058+ else if (buffer == "x-small") unit.value.FLOAT = 0.75;
1059+ else if (buffer == "small") unit.value.FLOAT = 0.89;
1060+ else if (buffer == "medium") unit.value.FLOAT = 1;
1061+ else if (buffer == "large") unit.value.FLOAT = 1.2;
1062+ else if (buffer == "x-large") unit.value.FLOAT = 1.5;
1063+ else if (buffer == "xx-large") unit.value.FLOAT = 2;
1064+ else if (buffer == "xxx-large") unit.value.FLOAT = 3;
1065+ found = true;
1066+ } else if (buffer.ends_with("baseline")) {
1067+ unit.type = UnitType::BASELINE;
1068+ std::cout << buffer << std::endl;
1069+ if (buffer.starts_with("first")) unit.value.ENUM = UnitType::FIRST;
1070+ else if (buffer.starts_with("last")) unit.value.ENUM = UnitType::LAST;
1071+ else unit.value.ENUM = UnitType::FIRST;
1072+ found = true;
1073+ } else if (buffer.ends_with("-box")) {
1074+ unit.type = UnitType::BOX_EDGE;
1075+
1076+ if (buffer.starts_with("content")) unit.value.ENUM = UnitType::CONTENT;
1077+ else if (buffer.starts_with("padding")) unit.value.ENUM = UnitType::PADDING;
1078+ else if (buffer.starts_with("border")) unit.value.ENUM = UnitType::BORDER;
1079+ else if (buffer.starts_with("margin")) unit.value.ENUM = UnitType::MARGIN;
1080+ else if (buffer.starts_with("fill")) unit.value.ENUM = UnitType::FILL;
1081+ else if (buffer.starts_with("stroke")) unit.value.ENUM = UnitType::STROKE;
1082+ else if (buffer.starts_with("view")) unit.value.ENUM = UnitType::VIEW;
1083+ found = true;
1084+ }
1085+ }
1086+
1087+ if (found) {
1088+ // Only process if its found
1089+
1090+ if (children.size() > 0) {
1091+ unit.children = children;
1092+ children.clear();
1093+ }
1094+
1095+ units.push_back(unit);
1096+
1097+ buffer.clear();
1098+ }
1099+ } else {
1100+ buffer.push_back(v);
1101+ }
1102+
1103+ escaped = false;
1104+ if (v == '\\') escaped = true;
1105+
1106+ }
1107+
1108+ return units;
1109+}
1110+
1111+void StyleHandler::parseStream(std::istream& inputStream) {
1112+ // nowhitespace: value(can have whitespace); = property
1113+ // a selector name is anything before a { up to a ; or a }
1114+ Sheet* root = &window->CSS.root;
1115+
1116+ Sheet* current = root;
1117+ // how to handle @import
1118+
1119+ std::string buffer;
1120+
1121+ bool incomment = false;
1122+
1123+ char ch;
1124+ while (inputStream.get(ch)) {
1125+ if (ch == '*' && inputStream.peek() == '/') {
1126+ incomment = false;
1127+ // Skip ahead to the next letter so we don't caputure the trailing /
1128+ inputStream.get(ch);
1129+ continue;
1130+ }
1131+ if (ch == '/' && inputStream.peek() == '*') {
1132+ incomment = true;
1133+ }
1134+
1135+ if (incomment) {
1136+ continue;
1137+ }
1138+
1139+ if (ch == '{') {
1140+ for (size_t i = buffer.length()-1; i >= 0; i--) {
1141+ if (std::isspace(buffer[i])) {
1142+ buffer.pop_back();
1143+ } else {
1144+ break;
1145+ }
1146+ }
1147+
1148+ // Parse the selector into its parts
1149+ std::vector<std::vector<std::string>> parts = parseSelectorParts(buffer);
1150+
1151+ if (current->selector.size() > 0) {
1152+ // To be able to handle a subsutution with a parent that has h1, h2
1153+ // we need to rebuild the selector so we can add new copies
1154+ std::vector<std::vector<std::string>> subd;
1155+
1156+ // Look for any & if its a style and replace it with the parsed parent selector
1157+ for (size_t i = 0; i < parts.size(); i++) {
1158+ bool hasAmp = false;
1159+ for (size_t e = 0; e < parts[i].size(); e++) {
1160+ if (parts[i][e] == "&") {
1161+ hasAmp = true;
1162+ break;
1163+ }
1164+ }
1165+
1166+ if (hasAmp) {
1167+ // If it contains a amp then we need replace the amp with each parent selector
1168+ // Inject each part of the parent
1169+ // Parent [[h1],[h2]]
1170+ // Child: [[& .class, &, &]]
1171+ // Result: [[h1 .class, h1, h1]]
1172+ // [[h2 .class, h2, h2]]
1173+ for (size_t e = 0; e < current->selector.size(); e++) {
1174+ std::vector<std::string> temp;
1175+ for (size_t f = 0; f < parts[i].size(); f++) {
1176+ if (parts[i][f] == "&") {
1177+ for (auto p : current->selector[e]) {
1178+ temp.push_back(p);
1179+ }
1180+ } else {
1181+ temp.push_back(parts[i][f]);
1182+ }
1183+ }
1184+ subd.push_back(temp);
1185+ }
1186+ } else {
1187+ // If theres nothing to replace just add to back
1188+ subd.push_back(parts[i]);
1189+ }
1190+ }
1191+
1192+ parts = subd;
1193+ }
1194+
1195+ // Find who the parent is
1196+ Sheet newSheet;
1197+ newSheet.parent = current;
1198+ newSheet.selector = parts;
1199+ current->children.push_back(newSheet);
1200+
1201+ current = ¤t->children.back();
1202+
1203+ // here we need to take the selector (buffer)
1204+ // and find out what type it is. Also need to manage the nesting
1205+ if (parts[0][0][0] == '@') {
1206+ if (parts[0][0] == "@container") {
1207+ current->type = SheetType::CONTAINER;
1208+ bool hasName = false;
1209+ if (parts[0].size() >= 3) {
1210+ // @container not (width < 400px)
1211+ if (parts[0][2] != "not") {
1212+ // @container (width > 400px) or|and (height > 400px)
1213+ if (parts[0].size() >= 5) {
1214+ if (parts[0][5] != "and" || parts[0][5] != "or") {
1215+ current->name = parts[0][2];
1216+ hasName = true;
1217+ }
1218+ }
1219+ }
1220+ }
1221+ // If a name is present then remove the name and tag from the selector
1222+ // else just remove the @container
1223+ if (hasName) {
1224+ current->selector[0] = std::vector<std::string>(current->selector[0].begin() + 3, current->selector[0].end());
1225+ } else {
1226+ current->selector[0] = std::vector<std::string>(current->selector[0].begin() + 1, current->selector[0].end());
1227+ }
1228+ } else if (parts[0][0] == "@counter-style") {
1229+ current->name = parts[0][2];
1230+ current->type = SheetType::COUNTER;
1231+ } else if (parts[0][0] == "@font-face") {
1232+ current->type = SheetType::FONTFACE;
1233+ } else if (parts[0][0] == "@font-feature") {
1234+ current->type = SheetType::FONTFEATURE;
1235+ } else if (parts[0][0] == "@swash") {
1236+ current->type = SheetType::SWASH;
1237+ } else if (parts[0][0] == "@annotation") {
1238+ current->type = SheetType::ANNOTATION;
1239+ } else if (parts[0][0] == "@ornaments") {
1240+ current->type = SheetType::ORNAMENTS;
1241+ } else if (parts[0][0] == "@stylistic") {
1242+ current->type = SheetType::STYLISTIC;
1243+ } else if (parts[0][0] == "@styleset") {
1244+ current->type = SheetType::STYLESET;
1245+ } else if (parts[0][0] == "@character-varient") {
1246+ current->type = SheetType::CHARACTERVARIANT;
1247+ } else if (parts[0][0] == "@font-palette-values") {
1248+ current->type = SheetType::FONTPALETTEVALUES;
1249+ current->name = parts[0][2];
1250+ } else if (parts[0][0] == "@keyframes") {
1251+ current->type = SheetType::KEYFRAMES;
1252+ current->name = parts[0][2];
1253+ } else if (parts[0][0] == "@layer") {
1254+ current->type = SheetType::LAYER;
1255+ } else if (parts[0][0] == "@media") {
1256+ current->type = SheetType::MEDIA;
1257+ } else if (parts[0][0] == "@property") {
1258+ current->type = SheetType::PROPERTY;
1259+ current->name = parts[0][2];
1260+ } else if (parts[0][0] == "@starting-style") {
1261+ current->type = SheetType::STARTINGSTYLE;
1262+ }
1263+ } else {
1264+ current->type = SheetType::STYLE;
1265+ }
1266+
1267+
1268+ buffer.clear();
1269+ } else if (ch == '}') {
1270+ // Calculate the basemap (more info in grim.h)
1271+ std::vector<std::string> targetParts = current->selector[current->selector.size()-1];
1272+ for (auto p : targetParts) {
1273+ if (current->parent->basemap.find(p) != current->parent->basemap.end()) {
1274+ current->parent->basemap[p].push_back(window->CSS.index);
1275+ } else {
1276+ std::vector<size_t> bm = {window->CSS.index};
1277+ current->parent->basemap[p] = bm;
1278+ }
1279+ }
1280+
1281+ window->CSS.index++;
1282+
1283+ if (current->parent != nullptr) {
1284+ current = current->parent;
1285+ }
1286+
1287+ buffer.clear();
1288+ } else if (ch == ';') {
1289+ if (buffer[0] == '@') {
1290+ // Single line at-rule
1291+ Sheet inlineRule;
1292+ std::vector<std::vector<std::string>> parts = parseSelectorParts(buffer);
1293+ inlineRule.selector = parts;
1294+
1295+ if (parts[0][0] == "@import") {
1296+ inlineRule.path = parts[0][2];
1297+
1298+ if (parts[0].size() > 3) {
1299+ // See if the last word ends in )
1300+ // if not then its the name of the layer
1301+ if (parts[0].back().back() != ')') {
1302+ inlineRule.name = parts[0].back();
1303+ }
1304+ }
1305+
1306+ } else if (parts[0][0] == "@layer") {
1307+ // ISSUE: need to add speficity
1308+
1309+ }
1310+
1311+ current->children.push_back(inlineRule);
1312+
1313+ } else {
1314+ // end of a style property
1315+ std::string key;
1316+ std::string value;
1317+ bool keyFound = false;
1318+ bool firstValue = false;
1319+ for (size_t i = 0; i < buffer.length(); i++) {
1320+ char c = buffer[i];
1321+ if (!keyFound && c == ':') {
1322+ keyFound = true;
1323+ } else if (!std::isspace(c) && !keyFound) {
1324+ key += c;
1325+ } else if (!firstValue && keyFound && !std::isspace(c)) {
1326+ firstValue = true;
1327+ value += c;
1328+ } else if (keyFound && firstValue && !(std::isspace(c) && i < buffer.length()-1 && std::isspace(buffer[i+1]))) {
1329+ // if the key is found and the charactor isn't a ':'
1330+ // also (if c is a space and the next c will be a space)
1331+ // this is to auto trim duplicate spaces from the value
1332+ value += c;
1333+ }
1334+ }
1335+
1336+ if (key.length() > 2 && key[0] == '-' && key[1] == '-') {
1337+ current->variables[key] = value;
1338+ } else {
1339+ current->properties[key] = value;
1340+ }
1341+ }
1342+ buffer.clear();
1343+ } else {
1344+ buffer.push_back(ch);
1345+ }
1346+ }
1347+}
1348+
1349+std::unordered_map<std::string, std::string> parseCSSInline(std::string inlineStyles) {
1350+ std::unordered_map<std::string, std::string> props;
1351+
1352+ std::string buffer;
1353+
1354+ for (size_t i = 0; i < inlineStyles.length(); i++) {
1355+ char ch = inlineStyles[i];
1356+
1357+ if (ch == ';' || i >= inlineStyles.length()-1) {
1358+ // If this is a ';' then it will be removed in the last else if statement
1359+ buffer.push_back(ch);
1360+ std::string key;
1361+ std::string value;
1362+ bool keyFound = false;
1363+ for (size_t i = 0; i < buffer.length(); i++) {
1364+ char c = buffer[i];
1365+ if (!keyFound && c == ':') {
1366+ keyFound = true;
1367+ } else if (!std::isspace(c) && !keyFound) {
1368+ key += c;
1369+ } else if (keyFound &&
1370+ !(std::isspace(c) && i < buffer.length()-1 && std::isspace(buffer[i+1])) &&
1371+ c != ';'
1372+ ) {
1373+ // if the key is found and the charactor isn't a ':'
1374+ // also (if c is a space and the next c will be a space)
1375+ // this is to auto trim duplicate spaces from the value
1376+ value += c;
1377+
1378+ }
1379+ }
1380+
1381+ props[key] = value;
1382+
1383+ buffer.clear();
1384+ } else {
1385+ buffer.push_back(ch);
1386+ }
1387+ }
1388+
1389+ return props;
1390+}
1391+
1392+
1393diff --git a/tests/css_unit.cc b/tests/css_unit.cc
1394index 239e7fa..0198810 100644
1395--- a/tests/css_unit.cc
1396+++ b/tests/css_unit.cc
1397@@ -6,47 +6,630 @@
1398
1399
1400 TEST_CASE("getUnit can parse color values") {
1401-
1402+/*
1403 unsigned int base = 0xff7dff1a;
1404
1405 SECTION("getUnit can parse hex") {
1406- Unit test = getUnit("background-color", "#ff7dff1a");
1407+ Unit test = getUnit("#ff7dff1a");
1408 INFO(test.value.UINT);
1409 REQUIRE(base == test.value.UINT);
1410 };
1411
1412 SECTION("getUnit can parse rgba") {
1413- Unit test = getUnit("background-color", "rgba(255 125 255 / 10%)");
1414+ Unit test = getUnit("rgba(255 125 255 / 10%)");
1415 INFO(test.value.UINT);
1416 // Due to type casting 0.1 will return 25 not 26 or 19 vs 1a
1417 REQUIRE(0xff7dff19 == test.value.UINT);
1418 }
1419
1420 SECTION("getUnit can parse rgb") {
1421- Unit test = getUnit("background-color", "rgb(255 125 255)");
1422+ Unit test = getUnit("rgb(255 125 255)");
1423 INFO(test.value.UINT);
1424 // Due to type casting 0.1 will return 25 not 26 or 19 vs 1a
1425 REQUIRE(0xff7dff00 == test.value.UINT);
1426 }
1427
1428 SECTION("getUnit can parse hsl") {
1429- Unit test = getUnit("background-color", "hsl(300deg 100% 74.51%)");
1430+ Unit test = getUnit("hsl(300deg 100% 74.51%)");
1431 INFO(test.value.UINT);
1432 // Due to type casting 0.1 will return 25 not 26 or 19 vs 1a
1433 REQUIRE(0xff7afe00 == test.value.UINT);
1434 }
1435
1436 SECTION("getUnit can parse hsla") {
1437- Unit test = getUnit("background-color", "hsl(300deg 100% 74.51% / 10%)");
1438+ Unit test = getUnit("hsl(300deg 100% 74.51% / 10%)");
1439 INFO(test.value.UINT);
1440 // Due to type casting 0.1 will return 25 not 26 or 19 vs 1a
1441 REQUIRE(0xff7afe19 == test.value.UINT);
1442 }
1443
1444 SECTION("getUnit can parse hwb") {
1445- Unit test = getUnit("background-color", "hwb(80deg 20 50 / 50%)");
1446+ Unit test = getUnit("hwb(80deg 20 50 / 50%)");
1447 INFO(test.value.UINT);
1448 // Due to type casting 0.1 will return 25 not 26 or 19 vs 1a
1449 REQUIRE(0x667f337f == test.value.UINT);
1450+ }*/
1451+ // px
1452+ SECTION("getUnit can parse px") {
1453+ std::vector<Unit> test = getUnit("10px 10.5px");
1454+ REQUIRE(10.0f == test[0].value.FLOAT);
1455+ REQUIRE(UnitType::PX == test[0].type);
1456+ REQUIRE(10.5f == test[1].value.FLOAT);
1457+ REQUIRE(UnitType::PX == test[1].type);
1458+ }
1459+
1460+ // em
1461+ SECTION("getUnit can parse em") {
1462+ std::vector<Unit> test = getUnit("1.2em 0em");
1463+ REQUIRE(1.2f == test[0].value.FLOAT);
1464+ REQUIRE(UnitType::EM == test[0].type);
1465+ REQUIRE(0.0f == test[1].value.FLOAT);
1466+ REQUIRE(UnitType::EM == test[1].type);
1467+ }
1468+
1469+ // %
1470+ SECTION("getUnit can parse percent") {
1471+ std::vector<Unit> test = getUnit("50% 100.5%");
1472+ REQUIRE(50.0f == test[0].value.FLOAT);
1473+ REQUIRE(UnitType::PERCENT == test[0].type);
1474+ REQUIRE(100.5f == test[1].value.FLOAT);
1475+ REQUIRE(UnitType::PERCENT == test[1].type);
1476+ }
1477+
1478+ // cm
1479+ SECTION("getUnit can parse cm") {
1480+ std::vector<Unit> test = getUnit("2.54cm 0.5cm");
1481+ REQUIRE(2.54f == test[0].value.FLOAT);
1482+ REQUIRE(UnitType::CM == test[0].type);
1483+ REQUIRE(0.5f == test[1].value.FLOAT);
1484+ REQUIRE(UnitType::CM == test[1].type);
1485+ }
1486+
1487+ // cap
1488+ SECTION("getUnit can parse cap") {
1489+ std::vector<Unit> test = getUnit("3cap 0cap");
1490+ REQUIRE(3.0f == test[0].value.FLOAT);
1491+ REQUIRE(UnitType::CAP == test[0].type);
1492+ REQUIRE(0.0f == test[1].value.FLOAT);
1493+ REQUIRE(UnitType::CAP == test[1].type);
1494+ }
1495+
1496+ // ch
1497+ SECTION("getUnit can parse ch") {
1498+ std::vector<Unit> test = getUnit("10ch 0.5ch");
1499+ REQUIRE(10.0f == test[0].value.FLOAT);
1500+ REQUIRE(UnitType::CH == test[0].type);
1501+ REQUIRE(0.5f == test[1].value.FLOAT);
1502+ REQUIRE(UnitType::CH == test[1].type);
1503+ }
1504+
1505+ // ex
1506+ SECTION("getUnit can parse ex") {
1507+ std::vector<Unit> test = getUnit("2ex 1.5ex");
1508+ REQUIRE(2.0f == test[0].value.FLOAT);
1509+ REQUIRE(UnitType::EX == test[0].type);
1510+ REQUIRE(1.5f == test[1].value.FLOAT);
1511+ REQUIRE(UnitType::EX == test[1].type);
1512+ }
1513+
1514+ // ic
1515+ SECTION("getUnit can parse ic") {
1516+ std::vector<Unit> test = getUnit("5ic 0ic");
1517+ REQUIRE(5.0f == test[0].value.FLOAT);
1518+ REQUIRE(UnitType::IC == test[0].type);
1519+ REQUIRE(0.0f == test[1].value.FLOAT);
1520+ REQUIRE(UnitType::IC == test[1].type);
1521+ }
1522+
1523+ // lh
1524+ SECTION("getUnit can parse lh") {
1525+ std::vector<Unit> test = getUnit("1.2lh 0lh");
1526+ REQUIRE(1.2f == test[0].value.FLOAT);
1527+ REQUIRE(UnitType::LH == test[0].type);
1528+ REQUIRE(0.0f == test[1].value.FLOAT);
1529+ REQUIRE(UnitType::LH == test[1].type);
1530+ }
1531+
1532+ // rcap
1533+ SECTION("getUnit can parse rcap") {
1534+ std::vector<Unit> test = getUnit("2rcap 0.5rcap");
1535+ REQUIRE(2.0f == test[0].value.FLOAT);
1536+ REQUIRE(UnitType::RCAP == test[0].type);
1537+ REQUIRE(0.5f == test[1].value.FLOAT);
1538+ REQUIRE(UnitType::RCAP == test[1].type);
1539+ }
1540+
1541+ // rch
1542+ SECTION("getUnit can parse rch") {
1543+ std::vector<Unit> test = getUnit("3rch 0rch");
1544+ REQUIRE(3.0f == test[0].value.FLOAT);
1545+ REQUIRE(UnitType::RCH == test[0].type);
1546+ REQUIRE(0.0f == test[1].value.FLOAT);
1547+ REQUIRE(UnitType::RCH == test[1].type);
1548+ }
1549+
1550+ // rem
1551+ SECTION("getUnit can parse rem") {
1552+ std::vector<Unit> test = getUnit("1rem 0.5rem");
1553+ REQUIRE(1.0f == test[0].value.FLOAT);
1554+ REQUIRE(UnitType::REM == test[0].type);
1555+ REQUIRE(0.5f == test[1].value.FLOAT);
1556+ REQUIRE(UnitType::REM == test[1].type);
1557+ }
1558+
1559+ // rex
1560+ SECTION("getUnit can parse rex") {
1561+ std::vector<Unit> test = getUnit("1.5rex 0rex");
1562+ REQUIRE(1.5f == test[0].value.FLOAT);
1563+ REQUIRE(UnitType::REX == test[0].type);
1564+ REQUIRE(0.0f == test[1].value.FLOAT);
1565+ REQUIRE(UnitType::REX == test[1].type);
1566+ }
1567+
1568+ // ric
1569+ SECTION("getUnit can parse ric") {
1570+ std::vector<Unit> test = getUnit("2ric 0.25ric");
1571+ REQUIRE(2.0f == test[0].value.FLOAT);
1572+ REQUIRE(UnitType::RIC == test[0].type);
1573+ REQUIRE(0.25f == test[1].value.FLOAT);
1574+ REQUIRE(UnitType::RIC == test[1].type);
1575+ }
1576+
1577+ // rlh
1578+ SECTION("getUnit can parse rlh") {
1579+ std::vector<Unit> test = getUnit("1.1rlh 0rlh");
1580+ REQUIRE(1.1f == test[0].value.FLOAT);
1581+ REQUIRE(UnitType::RLH == test[0].type);
1582+ REQUIRE(0.0f == test[1].value.FLOAT);
1583+ REQUIRE(UnitType::RLH == test[1].type);
1584+ }
1585+
1586+ // vh
1587+ SECTION("getUnit can parse vh") {
1588+ std::vector<Unit> test = getUnit("50vh 100vh");
1589+ REQUIRE(50.0f == test[0].value.FLOAT);
1590+ REQUIRE(UnitType::VH == test[0].type);
1591+ REQUIRE(100.0f == test[1].value.FLOAT);
1592+ REQUIRE(UnitType::VH == test[1].type);
1593+ }
1594+
1595+ // vw
1596+ SECTION("getUnit can parse vw") {
1597+ std::vector<Unit> test = getUnit("25vw 0vw");
1598+ REQUIRE(25.0f == test[0].value.FLOAT);
1599+ REQUIRE(UnitType::VW == test[0].type);
1600+ REQUIRE(0.0f == test[1].value.FLOAT);
1601+ REQUIRE(UnitType::VW == test[1].type);
1602+ }
1603+
1604+ // vmax
1605+ SECTION("getUnit can parse vmax") {
1606+ std::vector<Unit> test = getUnit("75vmax 0vmax");
1607+ REQUIRE(75.0f == test[0].value.FLOAT);
1608+ REQUIRE(UnitType::VMAX == test[0].type);
1609+ REQUIRE(0.0f == test[1].value.FLOAT);
1610+ REQUIRE(UnitType::VMAX == test[1].type);
1611+ }
1612+
1613+ // vmin
1614+ SECTION("getUnit can parse vmin") {
1615+ std::vector<Unit> test = getUnit("10vmin 99.9vmin");
1616+ REQUIRE(10.0f == test[0].value.FLOAT);
1617+ REQUIRE(UnitType::VMIN == test[0].type);
1618+ REQUIRE(99.9f == test[1].value.FLOAT);
1619+ REQUIRE(UnitType::VMIN == test[1].type);
1620 }
1621+
1622+ // vb
1623+ SECTION("getUnit can parse vb") {
1624+ std::vector<Unit> test = getUnit("20vb 0vb");
1625+ REQUIRE(20.0f == test[0].value.FLOAT);
1626+ REQUIRE(UnitType::VB == test[0].type);
1627+ REQUIRE(0.0f == test[1].value.FLOAT);
1628+ REQUIRE(UnitType::VB == test[1].type);
1629+ }
1630+
1631+ // vi
1632+ SECTION("getUnit can parse vi") {
1633+ std::vector<Unit> test = getUnit("5vi 1vi");
1634+ REQUIRE(5.0f == test[0].value.FLOAT);
1635+ REQUIRE(UnitType::VI == test[0].type);
1636+ REQUIRE(1.0f == test[1].value.FLOAT);
1637+ REQUIRE(UnitType::VI == test[1].type);
1638+ }
1639+
1640+ // cqw
1641+ SECTION("getUnit can parse cqw") {
1642+ std::vector<Unit> test = getUnit("30cqw 0cqw");
1643+ REQUIRE(30.0f == test[0].value.FLOAT);
1644+ REQUIRE(UnitType::CQW == test[0].type);
1645+ REQUIRE(0.0f == test[1].value.FLOAT);
1646+ REQUIRE(UnitType::CQW == test[1].type);
1647+ }
1648+
1649+ // cqh
1650+ SECTION("getUnit can parse cqh") {
1651+ std::vector<Unit> test = getUnit("40cqh 10.5cqh");
1652+ REQUIRE(40.0f == test[0].value.FLOAT);
1653+ REQUIRE(UnitType::CQH == test[0].type);
1654+ REQUIRE(10.5f == test[1].value.FLOAT);
1655+ REQUIRE(UnitType::CQH == test[1].type);
1656+ }
1657+
1658+ // cqi
1659+ SECTION("getUnit can parse cqi") {
1660+ std::vector<Unit> test = getUnit("15cqi 0cqi");
1661+ REQUIRE(15.0f == test[0].value.FLOAT);
1662+ REQUIRE(UnitType::CQI == test[0].type);
1663+ REQUIRE(0.0f == test[1].value.FLOAT);
1664+ REQUIRE(UnitType::CQI == test[1].type);
1665+ }
1666+
1667+ // cqb
1668+ SECTION("getUnit can parse cqb") {
1669+ std::vector<Unit> test = getUnit("25cqb 1cqb");
1670+ REQUIRE(25.0f == test[0].value.FLOAT);
1671+ REQUIRE(UnitType::CQB == test[0].type);
1672+ REQUIRE(1.0f == test[1].value.FLOAT);
1673+ REQUIRE(UnitType::CQB == test[1].type);
1674+ }
1675+
1676+ // cqmin
1677+ SECTION("getUnit can parse cqmin") {
1678+ std::vector<Unit> test = getUnit("10cqmin 0cqmin");
1679+ REQUIRE(10.0f == test[0].value.FLOAT);
1680+ REQUIRE(UnitType::CQMIN == test[0].type);
1681+ REQUIRE(0.0f == test[1].value.FLOAT);
1682+ REQUIRE(UnitType::CQMIN == test[1].type);
1683+ }
1684+
1685+ // cqmax
1686+ SECTION("getUnit can parse cqmax") {
1687+ std::vector<Unit> test = getUnit("90cqmax \n 50.5cqmax");
1688+ REQUIRE(90.0f == test[0].value.FLOAT);
1689+ REQUIRE(UnitType::CQMAX == test[0].type);
1690+ REQUIRE(50.5f == test[1].value.FLOAT);
1691+ REQUIRE(UnitType::CQMAX == test[1].type);
1692+ }
1693+
1694+ // mm
1695+ SECTION("getUnit can parse mm") {
1696+ std::vector<Unit> test = getUnit("10mm 0.1mm");
1697+ REQUIRE(10.0f == test[0].value.FLOAT);
1698+ REQUIRE(UnitType::MM == test[0].type);
1699+ REQUIRE(0.1f == test[1].value.FLOAT);
1700+ REQUIRE(UnitType::MM == test[1].type);
1701+ }
1702+
1703+ // q
1704+ SECTION("getUnit can parse q") {
1705+ std::vector<Unit> test = getUnit("40q, 1q");
1706+ REQUIRE(40.0f == test[0].value.FLOAT);
1707+ REQUIRE(UnitType::Q == test[0].type);
1708+ REQUIRE(1.0f == test[1].value.FLOAT);
1709+ REQUIRE(UnitType::Q == test[1].type);
1710+ }
1711+
1712+ // in
1713+ SECTION("getUnit can parse in") {
1714+ std::vector<Unit> test = getUnit("1in 0.5in");
1715+ REQUIRE(1.0f == test[0].value.FLOAT);
1716+ REQUIRE(UnitType::IN == test[0].type);
1717+ REQUIRE(0.5f == test[1].value.FLOAT);
1718+ REQUIRE(UnitType::IN == test[1].type);
1719+ }
1720+
1721+ // pc
1722+ SECTION("getUnit can parse pc") {
1723+ std::vector<Unit> test = getUnit("6pc, 0pc");
1724+ REQUIRE(6.0f == test[0].value.FLOAT);
1725+ REQUIRE(UnitType::PC == test[0].type);
1726+ REQUIRE(0.0f == test[1].value.FLOAT);
1727+ REQUIRE(UnitType::PC == test[1].type);
1728+ }
1729+
1730+ // pt
1731+ SECTION("getUnit can parse pt") {
1732+ std::vector<Unit> test = getUnit("12pt");
1733+ REQUIRE(12.0f == test[0].value.FLOAT);
1734+ REQUIRE(UnitType::PT == test[0].type);
1735+ }
1736+
1737+ // HEX Color
1738+ SECTION("getUnit can parse a hex color") {
1739+ std::vector<Unit> test = getUnit("#000000");
1740+ REQUIRE(0x000000 == test[0].value.UINT);
1741+ REQUIRE(UnitType::HEX == test[0].type);
1742+ }
1743+
1744+ // xx-small abs unit
1745+ SECTION("getUnit can parse a xx-small abs unit") {
1746+ std::vector<Unit> test = getUnit("xx-small");
1747+ REQUIRE(0.6f == test[0].value.FLOAT);
1748+ REQUIRE(UnitType::EM == test[0].type);
1749+ }
1750+
1751+ // x-small abs unit
1752+ SECTION("getUnit can parse an x-small abs unit") {
1753+ std::vector<Unit> test = getUnit("x-small");
1754+ REQUIRE(0.75f == test[0].value.FLOAT);
1755+ REQUIRE(UnitType::EM == test[0].type);
1756+ }
1757+
1758+ // small abs unit
1759+ SECTION("getUnit can parse a small abs unit") {
1760+ std::vector<Unit> test = getUnit("small");
1761+ REQUIRE(0.89f == test[0].value.FLOAT);
1762+ REQUIRE(UnitType::EM == test[0].type);
1763+ }
1764+
1765+ // medium abs unit
1766+ SECTION("getUnit can parse a medium abs unit") {
1767+ std::vector<Unit> test = getUnit("medium");
1768+ REQUIRE(1.0f == test[0].value.FLOAT);
1769+ REQUIRE(UnitType::EM == test[0].type);
1770+ }
1771+
1772+ // large abs unit
1773+ SECTION("getUnit can parse a large abs unit") {
1774+ std::vector<Unit> test = getUnit("large");
1775+ REQUIRE(1.2f == test[0].value.FLOAT);
1776+ REQUIRE(UnitType::EM == test[0].type);
1777+ }
1778+
1779+ // x-large abs unit
1780+ SECTION("getUnit can parse an x-large abs unit") {
1781+ std::vector<Unit> test = getUnit("x-large");
1782+ REQUIRE(1.5f == test[0].value.FLOAT);
1783+ REQUIRE(UnitType::EM == test[0].type);
1784+ }
1785+
1786+ // xx-large abs unit
1787+ SECTION("getUnit can parse an xx-large abs unit") {
1788+ std::vector<Unit> test = getUnit("xx-large");
1789+ REQUIRE(2.0f == test[0].value.FLOAT);
1790+ REQUIRE(UnitType::EM == test[0].type);
1791+ }
1792+
1793+ // xxx-large abs unit
1794+ SECTION("getUnit can parse an xxx-large abs unit") {
1795+ std::vector<Unit> test = getUnit("xxx-large");
1796+ REQUIRE(3.0f == test[0].value.FLOAT);
1797+ REQUIRE(UnitType::EM == test[0].type);
1798+ }
1799+
1800+ // small and large abs unit
1801+ SECTION("getUnit can parse two abs units with a space") {
1802+ std::vector<Unit> test = getUnit("small large");
1803+ REQUIRE(0.89f == test[0].value.FLOAT);
1804+ REQUIRE(1.2f == test[1].value.FLOAT);
1805+ REQUIRE(UnitType::EM == test[0].type);
1806+ REQUIRE(UnitType::EM == test[1].type);
1807+ }
1808+
1809+ // small and large abs unit
1810+ SECTION("getUnit can parse two abs units with a comma") {
1811+ std::vector<Unit> test = getUnit("small, large");
1812+ REQUIRE(0.89f == test[0].value.FLOAT);
1813+ REQUIRE(1.2f == test[1].value.FLOAT);
1814+ REQUIRE(UnitType::EM == test[0].type);
1815+ REQUIRE(UnitType::EM == test[1].type);
1816+ }
1817+
1818+ // baseline
1819+ SECTION("getUnit can parse baseline into first baseline") {
1820+ std::vector<Unit> test = getUnit("baseline");
1821+ REQUIRE(UnitType::FIRST == test[0].value.ENUM);
1822+ REQUIRE(UnitType::BASELINE == test[0].type);
1823+ }
1824+
1825+ // first baseline
1826+ SECTION("getUnit can parse first baseline") {
1827+ std::vector<Unit> test = getUnit("first baseline");
1828+ REQUIRE(UnitType::FIRST == test[0].value.ENUM);
1829+ REQUIRE(UnitType::BASELINE == test[0].type);
1830+ }
1831+
1832+ // last baseline
1833+ SECTION("getUnit can parse last baseline") {
1834+ std::vector<Unit> test = getUnit("last baseline");
1835+ REQUIRE(UnitType::LAST == test[0].value.ENUM);
1836+ REQUIRE(UnitType::BASELINE == test[0].type);
1837+ }
1838+
1839+ // last baseline with another space seperated value
1840+ SECTION("getUnit can parse last baseline with a space seperated value") {
1841+ std::vector<Unit> test = getUnit("last baseline 10px");
1842+ REQUIRE(UnitType::LAST == test[0].value.ENUM);
1843+ REQUIRE(UnitType::BASELINE == test[0].type);
1844+ REQUIRE(10.0f == test[1].value.FLOAT);
1845+ REQUIRE(UnitType::PX == test[1].type);
1846+ }
1847+
1848+ // content-box
1849+ SECTION("getUnit can parse content-box") {
1850+ std::vector<Unit> test = getUnit("content-box");
1851+ REQUIRE(UnitType::CONTENT == test[0].value.ENUM);
1852+ REQUIRE(UnitType::BOX_EDGE == test[0].type);
1853+ }
1854+
1855+ // padding-box
1856+ SECTION("getUnit can parse padding-box") {
1857+ std::vector<Unit> test = getUnit("padding-box");
1858+ REQUIRE(UnitType::PADDING == test[0].value.ENUM);
1859+ REQUIRE(UnitType::BOX_EDGE == test[0].type);
1860+ }
1861+
1862+ // border-box
1863+ SECTION("getUnit can parse border-box") {
1864+ std::vector<Unit> test = getUnit("border-box");
1865+ REQUIRE(UnitType::BORDER == test[0].value.ENUM);
1866+ REQUIRE(UnitType::BOX_EDGE == test[0].type);
1867+ }
1868+
1869+ // margin-box
1870+ SECTION("getUnit can parse margin-box") {
1871+ std::vector<Unit> test = getUnit("margin-box");
1872+ REQUIRE(UnitType::MARGIN == test[0].value.ENUM);
1873+ REQUIRE(UnitType::BOX_EDGE == test[0].type);
1874+ }
1875+
1876+ // fill-box
1877+ SECTION("getUnit can parse fill-box") {
1878+ std::vector<Unit> test = getUnit("fill-box");
1879+ REQUIRE(UnitType::FILL == test[0].value.ENUM);
1880+ REQUIRE(UnitType::BOX_EDGE == test[0].type);
1881+ }
1882+
1883+ // stroke-box
1884+ SECTION("getUnit can parse stroke-box") {
1885+ std::vector<Unit> test = getUnit("stroke-box");
1886+ REQUIRE(UnitType::STROKE == test[0].value.ENUM);
1887+ REQUIRE(UnitType::BOX_EDGE == test[0].type);
1888+ }
1889+
1890+ // view-box
1891+ SECTION("getUnit can parse view-box") {
1892+ std::vector<Unit> test = getUnit("view-box");
1893+ REQUIRE(UnitType::VIEW == test[0].value.ENUM);
1894+ REQUIRE(UnitType::BOX_EDGE == test[0].type);
1895+ }
1896+
1897+ // calc
1898+ SECTION("getUnit can parse calc") {
1899+ std::vector<Unit> test = getUnit("calc(10px)");
1900+ REQUIRE(UnitType::CALC == test[0].value.ENUM);
1901+ REQUIRE(UnitType::FUNC == test[0].type);
1902+
1903+ REQUIRE(10.0f == test[0].children[0].value.FLOAT);
1904+ REQUIRE(UnitType::PX == test[0].children[0].type);
1905+ }
1906+
1907+ SECTION("getUnit can parse calc with add") {
1908+ std::vector<Unit> test = getUnit("calc(10px + 14%)");
1909+ REQUIRE(UnitType::CALC == test[0].value.ENUM);
1910+ REQUIRE(UnitType::FUNC == test[0].type);
1911+
1912+ REQUIRE(10.0f == test[0].children[0].value.FLOAT);
1913+ REQUIRE(UnitType::PX == test[0].children[0].type);
1914+
1915+ REQUIRE(UnitType::ADD == test[0].children[1].value.ENUM);
1916+ REQUIRE(UnitType::OPERATOR == test[0].children[1].type);
1917+
1918+ REQUIRE(14.0f == test[0].children[2].value.FLOAT);
1919+ REQUIRE(UnitType::PERCENT == test[0].children[2].type);
1920+ }
1921+
1922+
1923+ // ---------- 1. Whitespace handling ----------
1924+ SECTION("calc ignores leading / trailing spaces and tabs") {
1925+ std::vector<Unit> t = getUnit(" calc( 50vmin ) ");
1926+ REQUIRE(t[0].type == UnitType::FUNC);
1927+ REQUIRE(t[0].value.ENUM == UnitType::CALC);
1928+ REQUIRE(t[0].children[0].value.FLOAT == 50.0f);
1929+ REQUIRE(t[0].children[0].type == UnitType::VMIN);
1930+ }
1931+
1932+ // ---------- 2. Subtraction ----------
1933+ SECTION("calc with subtraction") {
1934+ std::vector<Unit> t = getUnit("calc(100% - 2em)");
1935+ REQUIRE(t[0].value.ENUM == UnitType::CALC);
1936+ REQUIRE(t[0].children[1].value.ENUM == UnitType::SUB);
1937+ REQUIRE(t[0].children[1].type == UnitType::OPERATOR);
1938+ REQUIRE(t[0].children[2].value.FLOAT == 2.0f);
1939+ REQUIRE(t[0].children[2].type == UnitType::EM);
1940+ }
1941+
1942+ // ---------- 3. Multiplication ----------
1943+ SECTION("calc with multiply") {
1944+ std::vector<Unit> t = getUnit("calc(3 * 4.5rem)");
1945+ REQUIRE(t[0].children[1].value.ENUM == UnitType::MULT);
1946+ REQUIRE(t[0].children[2].value.FLOAT == 4.5f);
1947+ REQUIRE(t[0].children[2].type == UnitType::REM);
1948+ }
1949+
1950+ // ---------- 4. Division ----------
1951+ SECTION("calc with divide") {
1952+ std::vector<Unit> t = getUnit("calc(90deg / 3)");
1953+ REQUIRE(t[0].children[1].value.ENUM == UnitType::DIV);
1954+ REQUIRE(t[0].children[2].value.FLOAT == 3.0f);
1955+ REQUIRE(t[0].children[0].type == UnitType::DEG);
1956+ }
1957+
1958+ // ---------- 5. Chained operators ----------
1959+ SECTION("calc with three terms and two ops") {
1960+ std::vector<Unit> t = getUnit("calc(10px + 20px - 5px)");
1961+ REQUIRE(t[0].children.size() == 5); // px + px - px
1962+ REQUIRE(t[0].children[1].value.ENUM == UnitType::ADD);
1963+ REQUIRE(t[0].children[3].value.ENUM == UnitType::SUB);
1964+ }
1965+
1966+ // ---------- 6. Parentheses ----------
1967+ SECTION("calc nested parentheses") {
1968+ std::vector<Unit> t = getUnit("calc((2 * 3em) + 4px)");
1969+ // The first child should be the subtree for the parens
1970+ REQUIRE(t[0].type == UnitType::FUNC);
1971+ REQUIRE(t[0].children[0].value.ENUM == UnitType::GROUP);
1972+ REQUIRE(t[0].children[2].type == UnitType::PX);
1973+ }
1974+
1975+ // ---------- 7. Constants ----------
1976+ SECTION("calc with pi") {
1977+ std::vector<Unit> t = getUnit("calc(pi * 30px)");
1978+ REQUIRE(t[0].children[0].value.ENUM == UnitType::PI);
1979+ REQUIRE(t[0].children[1].value.ENUM == UnitType::MULT);
1980+ REQUIRE(t[0].children[2].value.FLOAT == 30.0f);
1981+ }
1982+
1983+ // ---------- 8. Negative numbers ----------
1984+ SECTION("calc with negative value") {
1985+ std::vector<Unit> t = getUnit("calc(-15vw + 10vw)");
1986+ REQUIRE(t[0].children[0].value.FLOAT == -15.0f);
1987+ REQUIRE(t[0].children[0].type == UnitType::VW);
1988+ }
1989+
1990+ // ---------- 9. Zero unit ----------
1991+ SECTION("calc with zero px") {
1992+ std::vector<Unit> t = getUnit("calc(0px + 5px)");
1993+ REQUIRE(t[0].children[0].value.FLOAT == 0.0f);
1994+ REQUIRE(t[0].children[0].type == UnitType::PX);
1995+ }
1996+
1997+ // ---------- 10. Scientific notation ----------
1998+ SECTION("calc with scientific notation") {
1999+ std::vector<Unit> t = getUnit("calc(1.2e3px)");
2000+ REQUIRE(t[0].children[0].value.FLOAT == 1200.0f);
2001+ REQUIRE(t[0].children[0].type == UnitType::PX);
2002+ }
2003+
2004+ // ---------- 11. Many spaces around operator ----------
2005+ SECTION("calc with huge whitespace gap") {
2006+ std::vector<Unit> t = getUnit("calc(50px * 2)");
2007+ REQUIRE(t[0].children[1].value.ENUM == UnitType::MULT);
2008+ }
2009+
2010+ // ---------- 12. Double negation ----------
2011+ SECTION("calc with double negation") {
2012+ std::vector<Unit> t = getUnit("calc(10px - -5px)");
2013+ REQUIRE(t[0].children[2].value.FLOAT == -5.0f);
2014+ REQUIRE(t[0].children[1].value.ENUM == UnitType::SUB);
2015+ }
2016+
2017+ // ---------- 13. Mixed units ----------
2018+ SECTION("calc mixed vw and px") {
2019+ std::vector<Unit> t = getUnit("calc(100vw - 20px)");
2020+ REQUIRE(t[0].children[0].type == UnitType::VW);
2021+ REQUIRE(t[0].children[2].type == UnitType::PX);
2022+ }
2023+
2024+ // ---------- 14. Functions inside calc ----------
2025+ SECTION("calc with nested function") {
2026+ std::vector<Unit> t = getUnit("calc(min(10px, 20px) + 5px)");
2027+ // first child should be the min() subtree
2028+ REQUIRE(t[0].children[0].type == UnitType::FUNC);
2029+ REQUIRE(t[0].children[2].value.FLOAT == 5.0f);
2030+ }
2031+
2032+
2033+
2034 }