home
readme
diff
tree
note
docs
0commit 09c195df02536b6a796bd648fce9669397b96109
1Author: Mason Wright <mason@lakefox.net>
2Date: Wed Jun 18 16:42:10 2025 -0600
3
4 Moved things around and made .h files
5
6diff --git a/build.sh b/build.sh
7deleted file mode 100755
8index 4439e39..0000000
9--- a/build.sh
10+++ /dev/null
11@@ -1 +0,0 @@
12-clang++ element.cc -std=c++23 -o main
13diff --git a/element.cc b/element.cc
14deleted file mode 100755
15index 4994d06..0000000
16--- a/element.cc
17+++ /dev/null
18@@ -1,551 +0,0 @@
19-#include <string>
20-#include <vector>
21-#include <fstream>
22-#include <sstream>
23-#include <unordered_map>
24-#include <iostream>
25-#include <memory>
26-#include <cctype>
27-#include <algorithm>
28-#include <stdexcept>
29-
30-
31-// --- THE MACRO DEFINITION ---
32-// This macro generates a getter and a setter for a specific attribute.
33-// _Type: The C++ data type (e.g., std::string, int, bool)
34-// _FuncNameSuffix: The PascalCase suffix for the getter/setter (e.g., Id, TabIndex)
35-// _AttrKeyString: The exact string literal key used in the Attributes map (e.g., "id", "tabindex")
36-#define GENERATE_ATTRIBUTE_ACCESSORS(_Type, _FuncNameSuffix, _AttrKeyString) \
37- _Type get##_FuncNameSuffix() const { \
38- return getAttribute<_Type>(_AttrKeyString); \
39- } \
40- void set##_FuncNameSuffix(_Type value) { \
41- setAttribute(_AttrKeyString, value); \
42- }
43-
44-
45-struct Styles {
46- std::vector<std::unordered_map<std::string, std::string>> stylesheets;
47- std::unordered_map<std::string, std::string> inlineStyles;
48- std::unordered_map<std::string, std::unordered_map<std::string, std::string>> psuedoStyles;
49-};
50-
51-struct Bounds {
52- int top;
53- int right;
54- int bottom;
55- int left;
56-};
57-
58-struct State {
59- // Bounds offset;
60- // Border border;
61- // std::vector<Background> background;
62- int width;
63- int height;
64- int z;
65- bool hidden;
66- int tabIndex;
67-};
68-
69-class ClassList {
70- private:
71- std::vector<std::string> values;
72- public:
73- std::string value() const {
74- if (values.empty()) {
75- return "";
76- }
77- std::string collection = values[0];
78- for (size_t i = 1; i < values.size(); ++i) {
79- collection += " " + values[i];
80- }
81- return collection;
82- }
83-
84- void add(std::string value) {
85- values.push_back(value);
86- }
87-
88- void remove(std::string value) {
89- auto it_prev = std::find(values.begin(), values.end(), value);
90- if (it_prev != values.end()) {
91- *it_prev = values.back(); // Overwrite prevous position with last element
92- values.pop_back(); // Remove the last element
93- }
94- }
95-};
96-
97-// !TODO: Cascade the styles during the parsing of the document and add the styles during the css parsing
98-// + all other styles should cascade when added
99-class Node {
100- private:
101- std::string TagName;
102- // !NOTE: ContentEditable only supports plaintext
103- std::unordered_map<std::string, std::string> Attributes;
104- public:
105- Node* parent;
106- std::vector<std::unique_ptr<Node>> children;
107- ClassList classList;
108-
109- Node() : parent(nullptr) {}
110- // Can't use macro on tag name
111- std::string getTagName() const { return TagName; }
112- void setTagName(const std::string& name) { TagName = name; }
113-
114- const std::unordered_map<std::string, std::string>& getAttributes() const {
115- return Attributes;
116- }
117-
118-
119- // --- Define Getters and Setters
120- // !TODO: Add all global attributes
121- // + Src: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes
122- GENERATE_ATTRIBUTE_ACCESSORS(std::string, Id, "id")
123- GENERATE_ATTRIBUTE_ACCESSORS(std::string, InnerText, "innerText") // If you want innerText as an attribute
124- GENERATE_ATTRIBUTE_ACCESSORS(bool, ContentEditable, "contenteditable")
125- GENERATE_ATTRIBUTE_ACCESSORS(std::string, Href, "href")
126- GENERATE_ATTRIBUTE_ACCESSORS(std::string, Src, "src")
127- GENERATE_ATTRIBUTE_ACCESSORS(std::string, Title, "title")
128- GENERATE_ATTRIBUTE_ACCESSORS(std::string, Value, "value")
129- GENERATE_ATTRIBUTE_ACCESSORS(int, TabIndex, "tabindex")
130- GENERATE_ATTRIBUTE_ACCESSORS(bool, Disabled, "disabled")
131- GENERATE_ATTRIBUTE_ACCESSORS(bool, Required, "required")
132- GENERATE_ATTRIBUTE_ACCESSORS(bool, Checked, "checked")
133-
134-
135- Node* createElement(std::string name) {
136- std::unique_ptr<Node> newNode = std::make_unique<Node>();
137- newNode->setTagName(name);
138- newNode->parent = this;
139- children.push_back(std::move(newNode));
140- return children.back().get();
141- }
142-
143-
144- // --- Generic setAttribute (Type to string conversion) ---
145- template<typename T>
146- void setAttribute(const std::string& name, const T& value) {
147- if constexpr (std::is_same_v<T, bool>) {
148- if (value) {
149- Attributes[name] = "";
150- } else {
151- Attributes.erase(name);
152- }
153- } else if constexpr (std::is_arithmetic_v<T>) { // Handles int, double, etc.
154- Attributes[name] = std::to_string(value);
155- } else {
156- // This static_assert will fire if you try to use setAttribute with a type
157- // that doesn't have a specific handling or a std::string overload.
158- static_assert(std::is_convertible_v<T, std::string>,
159- "setAttribute: Type cannot be converted to std::string automatically.");
160- // If it's convertible, let's try to convert it.
161- Attributes[name] = static_cast<std::string>(value);
162- }
163- }
164-
165- // Overload for std::string to avoid unnecessary conversion (and explicit to_string)
166- void setAttribute(const std::string& name, const std::string& value) {
167- Attributes[name] = value;
168- }
169-
170-
171- // --- Generic getAttribute (string to Type conversion) ---
172- template<typename T>
173- T getAttribute(const std::string& name) const {
174- auto it = Attributes.find(name);
175- if (it != Attributes.end()) {
176- const std::string& s = it->second; // Get the string value from the map
177-
178- // Use if constexpr to convert the string to the requested type T
179- if constexpr (std::is_same_v<T, int>) {
180- try {
181- return std::stoi(s);
182- } catch (const std::invalid_argument& e) {
183- std::cerr << "Warning: Invalid integer attribute value '" << s << "' for '" << name << "'. Defaulting to 0. " << e.what() << std::endl;
184- return 0;
185- } catch (const std::out_of_range& e) {
186- std::cerr << "Warning: Integer attribute value '" << s << "' for '" << name << "' out of range. Defaulting to 0. " << e.what() << std::endl;
187- return 0;
188- }
189- } else if constexpr (std::is_same_v<T, bool>) {
190- // Return true if string is not empty and not "false" (case-insensitive)
191- // Adjust this logic if you have different boolean attribute parsing rules
192- std::string lower_s = s;
193- std::transform(lower_s.begin(), lower_s.end(), lower_s.begin(),
194- [](unsigned char c){ return std::tolower(c); });
195- return (!lower_s.empty() && lower_s != "false");
196- } else if constexpr (std::is_same_v<T, double>) {
197- try {
198- return std::stod(s);
199- } catch (const std::invalid_argument& e) {
200- std::cerr << "Warning: Invalid double attribute value '" << s << "' for '" << name << "'. Defaulting to 0.0. " << e.what() << std::endl;
201- return 0.0;
202- } catch (const std::out_of_range& e) {
203- std::cerr << "Warning: Double attribute value '" << s << "' for '" << name << "' out of range. Defaulting to 0.0. " << e.what() << std::endl;
204- return 0.0;
205- }
206- } else {
207- // If a type is requested that isn't explicitly handled,
208- // this static_assert will cause a compile error.
209- static_assert(std::is_convertible_v<std::string, T>,
210- "getAttribute: Type conversion from std::string not implemented for this type.");
211- // If it's convertible, attempt a static_cast (might not be what you want for all types)
212- return static_cast<T>(s);
213- }
214- }
215- // Return a default-constructed T if attribute not found
216- // This relies on T having a default constructor (e.g., int() is 0, bool() is false, std::string() is empty)
217- return T();
218- }
219-
220- // Overload for std::string directly (avoids template instantiation and conversion overhead)
221- // This function will be preferred by the compiler when T is std::string.
222- std::string getAttribute(const std::string& name) const {
223- auto it = Attributes.find(name);
224- if (it != Attributes.end()) {
225- return it->second;
226- }
227- return ""; // Default for string attributes if not found
228- }
229-
230- void print(int indent = 0) const {
231- // Print indentation
232- for (int i = 0; i < indent; ++i) {
233- std::cout << " ";
234- }
235-
236- // Print node information
237- std::cout << "<" << getTagName();
238-
239- // Print attributes
240- for (const auto& attr_pair : getAttributes()) {
241- if (attr_pair.first == "innerText" || attr_pair.first == "tagName") {
242- continue;
243- }
244- std::cout << " " << attr_pair.first;
245- if (!attr_pair.second.empty()) { // Only print value if it's not empty (for boolean attributes)
246- std::cout << "=\"" << attr_pair.second << "\"";
247- }
248- }
249- std::cout << ">";
250-
251- // Print inner text if any
252- // Note: HTML whitespace rules are complex; this just prints it raw
253- if (!getInnerText().empty()) {
254- std::cout << "\n" << getInnerText();
255- }
256-
257- std::cout << std::endl;
258-
259- // Recursively call print for children
260- for (const auto& child : children) {
261- child->print(indent + 1); // Increase indent for children
262- }
263-
264- // Print closing tag if it's not a self-closing/root tag
265- for (int i = 0; i < indent; ++i) {
266- std::cout << " ";
267- }
268- std::cout << "</" << getTagName() << ">" << std::endl;
269- }
270-};
271-
272-std::unordered_map<std::string, std::string> parseAttributes(std::string token) {
273- std::unordered_map<std::string, std::string> attrs;
274-
275- bool inQuote = false;
276- bool escaped = false;
277- char quoteType = '"';
278-
279-
280- std::string word;
281- for (unsigned short i = 0; i < token.length(); i++) {
282- char current = token[i];
283- if ((current == '"' || current == '\'') && !escaped) {
284- if (inQuote && current == quoteType) {
285- inQuote = false;
286- } else if (!inQuote) {
287- inQuote = true;
288- quoteType = current;
289- }
290- }
291-
292- if (current == '\\') { //'
293- escaped = true;
294- continue;
295- }
296-
297- if ((!std::isspace(current) || inQuote) && i != token.length()-1) {
298- word += current;
299- } else {
300- if (i == token.length()-1) {
301- word += current;
302- }
303-
304- std::string name = "";
305- std::string value = "";
306- bool setValue = false;
307-
308- for (auto c : word) {
309- if (c == '=') {
310- setValue = true;
311- continue;
312- }
313-
314- if (setValue) {
315- value += c;
316- } else {
317- name += c;
318- }
319- }
320-
321- word = "";
322-
323- std::string trimmedName = "";
324- for (auto c : name) {
325- if (!std::isspace(c)) {
326- trimmedName += c;
327- }
328- }
329-
330- if (attrs.size() == 0) {
331- value = '"'+trimmedName+'"';
332- trimmedName = "tagName";
333- }
334-
335- std::string trimmedValue = "";
336- int sliceStart = 0;
337- int sliceEnd = value.length();
338-
339- if (value.length() >= 2) {
340- for (unsigned short t = 0; t < value.length(); t++) {
341- if (!std::isspace(value[t])) {
342- sliceStart = t;
343- break;
344- }
345- }
346-
347- for (int t = value.length()-1; t >= 0; t--) {
348- // Trim the trailing / on self closing elements if there isn't a space inbetween
349- if (!std::isspace(value[t]) && value[t] != '/') {
350- sliceEnd = t;
351- break;
352- }
353- }
354-
355- // Add and subtract 1 from each side to remove quotes
356- for (unsigned short t = sliceStart+1; t < sliceEnd; t++) {
357- trimmedValue += value[t];
358- }
359- }
360-
361- attrs[trimmedName] = trimmedValue;
362- }
363- }
364-
365- return attrs;
366-}
367-
368-std::unique_ptr<Node> parseStream(std::istream& inputStream) {
369- std::unique_ptr<Node> root = std::make_unique<Node>();
370- root->setTagName("root");
371-
372- Node* currentNode = root.get();
373-
374- bool inTag = false;
375- bool escaped = false;
376- bool inQuote = false;
377- bool inComment = false;
378- char quoteType = '"';
379-
380- std::string token;
381-
382- char current;
383- while (inputStream.get(current)) {
384-
385- // Finds the --> and removes it then resets inComment
386- if (inComment) {
387- // added the peek to prevent hitting on every -
388- if (current == '-' && inputStream.peek() == '-') {
389- char a,b;
390- // load the next two
391- if (inputStream.get(a) && inputStream.get(b)) {
392- // We know b == -
393- if (b == '>') {
394- // Close the comment
395- inComment = false;
396- }
397- }
398-
399- if (!inComment) {
400- // we don't put anything back and that puts us where we need to be
401- continue;
402- } else {
403- // Not the end
404- inputStream.putback(b);
405- inputStream.putback(a);
406- }
407- }
408- continue;
409- }
410-
411- if (inTag) {
412- if ((current == '"' || current == '\'') && !escaped) {
413- if (inQuote && current == quoteType) {
414- inQuote = false;
415- } else if (!inQuote) {
416- inQuote = true;
417- quoteType = current;
418- }
419- }
420-
421- if (current == '>' && !inQuote && !escaped) {
422- inTag = false;
423- // Even if the next tag is next still add a text tag as we can just check if its empty and remove it
424- // this check would still have to be done either way so we aren't wasting anything
425- if (token.length() > 0) {
426- bool empty = true;
427- bool closingTag = false;
428- for (int i = 0; i < token.length(); i++) {
429- auto c = token[i];
430- if (std::isspace(c)) {
431- continue;
432- } else if (c == '/') {
433- closingTag = true;
434- } else {
435- empty = false;
436- break;
437- }
438- }
439-
440- bool selfClosing = false;
441- int selfClosingPosition = token.length()-1;
442- if (!closingTag) {
443- for (int i = token.length()-1; i >= 0; i--) {
444- auto c = token[i];
445- if (std::isspace(c)) {
446- continue;
447- } else if (c == '/') {
448- selfClosingPosition = i;
449- selfClosing = true;
450- break;
451- } else {
452- break;
453- }
454- }
455- }
456-
457- if (!empty) {
458- if (selfClosing) {
459- //Create element and don't jump inside
460- // Use node instead of currentNode because we do not need to jump into the created element
461- auto attrs = parseAttributes(token.substr(0, selfClosingPosition));
462- auto node = currentNode->createElement(attrs["tagName"]);
463- for (auto const& pair : attrs) {
464- // All attributes are stored as strings so we can just throw them in
465- node->setAttribute(pair.first, pair.second);
466- }
467- } else if (closingTag) {
468- // Checksum and tree move
469- std::string tagName = "";
470- for (auto t : token) {
471- if (!std::isspace(t) && t != '/') {
472- tagName += t;
473- } else if (t == '/') {
474- // For closing tags we just want the name
475- continue;
476- } else if (tagName.length() > 0) {
477- break;
478- }
479- }
480- if (currentNode->getTagName() == tagName) {
481- currentNode = currentNode->parent;
482- } else {
483- std::cerr << "malformed html: closing tag (</" << tagName << ">) found for <" << currentNode->getTagName() << ">" << std::endl;
484- }
485- } else {
486- // Create a element and jump inside
487- auto attrs = parseAttributes(token);
488- currentNode = currentNode->createElement(attrs["tagName"]);
489- for (auto const& pair : attrs) {
490- // All attributes are stored as strings so we can just throw them in
491- currentNode->setAttribute(pair.first, pair.second);
492- }
493-
494- }
495- }
496- }
497-
498- token = "";
499- continue;
500- }
501- } else if (!escaped && current == '<') {
502- // if the next charector is a !
503- if (inputStream.peek() == '!') {
504- char a,b,c;
505- // load the next three charecters (includes !)
506- if (inputStream.get(a) && inputStream.get(b) && inputStream.get(c)) {
507- // We know a == !
508- if (b == '-' && c == '-') {
509- inComment = true;
510- }
511- }
512-
513- if (inComment) {
514- continue;
515- } else {
516- // Not a comment add all back
517- inputStream.putback(c);
518- inputStream.putback(b);
519- inputStream.putback(a);
520- }
521- }
522- inTag = true;
523-
524- // Heres where you actually make the text node and above the real nodes
525- // can also prob make the vector of tokens a single variable
526- // it really just needs to be a data string bc we know the type based on inTag
527-
528-
529- bool hasText = false;
530- for (auto t : token) {
531- if (!std::isspace(t)) {
532- hasText = true;
533- break;
534- }
535- }
536- if (hasText) {
537- auto node = currentNode->createElement("text");
538- node->setInnerText(token);
539- }
540-
541-
542- token = "";
543- continue;
544- }
545-
546- escaped = false;
547-
548- if (current == '\\') { //'
549- escaped = true;
550- continue;
551- }
552-
553- token += current;
554- }
555-
556- return root;
557-}
558-
559-
560-int main() {
561- // std::string html1 = "<h1></h1>";
562- // std::stringstream ss1(html1);
563-
564- std::ifstream inputFile("./index.html");
565- auto document = parseStream(inputFile);
566-
567- document->print();
568- return 0;
569-}
570diff --git a/include/element.h b/include/element.h
571new file mode 100644
572index 0000000..83d5155
573--- /dev/null
574+++ b/include/element.h
575@@ -0,0 +1,89 @@
576+// Node.h
577+#ifndef ELEMENT_H
578+#define ELEMENT_H
579+
580+#include <string>
581+#include <vector>
582+#include <unordered_map>
583+#include <memory>
584+
585+struct Styles {
586+ std::vector<std::unordered_map<std::string, std::string>> stylesheets;
587+ std::unordered_map<std::string, std::string> inlineStyles;
588+ std::unordered_map<std::string, std::unordered_map<std::string, std::string>> psuedoStyles;
589+};
590+
591+struct Bounds {
592+ int top;
593+ int right;
594+ int bottom;
595+ int left;
596+};
597+
598+struct State {
599+ int width;
600+ int height;
601+ int z;
602+ bool hidden;
603+ int tabIndex;
604+};
605+
606+class ClassList {
607+private:
608+ std::vector<std::string> values;
609+public:
610+ std::string value() const;
611+ void add(std::string value);
612+ void remove(std::string value);
613+};
614+
615+class Node {
616+private:
617+ std::string TagName;
618+ std::unordered_map<std::string, std::string> Attributes;
619+
620+ template<typename T>
621+ void setAttribute(const std::string& name, const T& value);
622+
623+ template<typename T>
624+ T getAttribute(const std::string& name) const;
625+
626+public:
627+ Node* parent;
628+ std::vector<std::unique_ptr<Node>> children;
629+ ClassList classList;
630+
631+ Node();
632+ std::string getTagName() const;
633+ void setTagName(const std::string& name);
634+ const std::unordered_map<std::string, std::string>& getAttributes() const;
635+
636+ // --- Define Getters and Setters
637+ // !TODO: Add all global attributes
638+ // + Src: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes
639+ // The macro should stay here as it's part of the class definition
640+ #define GENERATE_ATTRIBUTE_ACCESSORS(_Type, _FuncNameSuffix, _AttrKeyString) \
641+ _Type get##_FuncNameSuffix() const; \
642+ void set##_FuncNameSuffix(_Type value);
643+
644+ GENERATE_ATTRIBUTE_ACCESSORS(std::string, Id, "id")
645+ GENERATE_ATTRIBUTE_ACCESSORS(std::string, InnerText, "innerText")
646+ GENERATE_ATTRIBUTE_ACCESSORS(bool, ContentEditable, "contenteditable")
647+ GENERATE_ATTRIBUTE_ACCESSORS(std::string, Href, "href")
648+ GENERATE_ATTRIBUTE_ACCESSORS(std::string, Src, "src")
649+ GENERATE_ATTRIBUTE_ACCESSORS(std::string, Title, "title")
650+ GENERATE_ATTRIBUTE_ACCESSORS(std::string, Value, "value")
651+ GENERATE_ATTRIBUTE_ACCESSORS(int, TabIndex, "tabindex")
652+ GENERATE_ATTRIBUTE_ACCESSORS(bool, Disabled, "disabled")
653+ GENERATE_ATTRIBUTE_ACCESSORS(bool, Required, "required")
654+ GENERATE_ATTRIBUTE_ACCESSORS(bool, Checked, "checked")
655+
656+ Node* createElement(std::string name);
657+
658+ void setAttribute(const std::string& name, const std::string& value);
659+ std::string getAttribute(const std::string& name) const;
660+
661+ void print(int indent = 0) const;
662+};
663+
664+#endif // NODE_H
665diff --git a/include/parser.h b/include/parser.h
666new file mode 100644
667index 0000000..68f0246
668--- /dev/null
669+++ b/include/parser.h
670@@ -0,0 +1,16 @@
671+// parser.h
672+#ifndef PARSER_H
673+#define PARSER_H
674+
675+#include <memory>
676+#include <istream>
677+#include <unordered_map>
678+#include <string>
679+
680+class Node;
681+
682+// Declare the parsing functions
683+std::unordered_map<std::string, std::string> parseAttributes(std::string token);
684+std::unique_ptr<Node> parseStream(std::istream& inputStream);
685+
686+#endif
687diff --git a/main b/main
688index 688e6e3..06056be 100755
689Binary files a/main and b/main differ
690diff --git a/main.cc b/main.cc
691index 4a6554a..135fb3a 100755
692--- a/main.cc
693+++ b/main.cc
694@@ -1,35 +1,31 @@
695-#include <print> // For print and std::println
696-#include <string>
697-using namespace std;
698+#include <iostream>
699+#include <fstream>
700+#include <memory>
701+#include "element.h"
702+#include "parser.h"
703
704 int main() {
705- int age = 30;
706- string name = "Alice";
707- double pi = 3.1415926535;
708- bool is_cpp_fun = true;
709-
710- // 1. Basic usage - similar to cout without endl
711- print("Hello, world!");
712- print("This is awesome.\n"); // Manually add newline for print
713-
714- // 2. Basic usage with arguments - placeholder {}
715- println("Name: {}, Age: {}", name, age);
716-
717- // 3. Positional arguments (optional, but useful for reordering)
718- println("Age: {1}, Name: {0}", name, age);
719-
720- // 4. Formatting numbers (precision, width, scientific notation)
721- println("Pi (default): {}", pi);
722- println("Pi (2 decimal places): {:.2f}", pi);
723- println("Pi (scientific): {:e}", pi);
724- println("Number with width and fill: {:*^10}", 42); // Center 42 in 10 chars, fill with *
725-
726- // 5. Formatting boolean values
727- println("Is C++ fun? {}", is_cpp_fun); // Prints 1 or 0 by default
728- println("Is C++ fun? {:L}", is_cpp_fun); // Prints true or false (locale-dependent)
729-
730- // 6. Printing multiple values with spaces in between
731- println("{} {} {} {}", "One", 2, 3.0, true);
732+ // Option 1: Parse from a file
733+ std::ifstream inputFile("./index.html");
734+ if (!inputFile.is_open()) {
735+ std::cerr << "Error: Could not open index.html" << std::endl;
736+ return 1;
737+ }
738+
739+ std::unique_ptr<Node> document = parseStream(inputFile);
740+ inputFile.close(); // Close the file after parsing
741+
742+ // Option 2: Parse from a string (for testing)
743+ // std::string html_content = "<div>Hello <b>world</b>!</div><p>Another paragraph.</p>";
744+ // std::stringstream ss(html_content);
745+ // std::unique_ptr<Node> document = parseStream(ss);
746+
747+ // Print the parsed document tree
748+ if (document) {
749+ document->print();
750+ } else {
751+ std::cout << "Document parsing failed or resulted in an empty document." << std::endl;
752+ }
753
754 return 0;
755 }
756diff --git a/make.sh b/make.sh
757new file mode 100755
758index 0000000..5498ebc
759--- /dev/null
760+++ b/make.sh
761@@ -0,0 +1 @@
762+clang++ -I./include main.cc src/element.cc src/parser.cc -std=c++23 -o main
763diff --git a/adapter.cc b/src/adapter.cc
764similarity index 100%
765rename from adapter.cc
766rename to src/adapter.cc
767diff --git a/src/element.cc b/src/element.cc
768new file mode 100755
769index 0000000..157533f
770--- /dev/null
771+++ b/src/element.cc
772@@ -0,0 +1,166 @@
773+#include "element.h"
774+#include <iostream>
775+#include <sstream>
776+#include <algorithm>
777+#include <stdexcept>
778+#include <cctype>
779+
780+std::string ClassList::value() const {
781+ if (values.empty()) {
782+ return "";
783+ }
784+ std::string collection = values[0];
785+ for (size_t i = 1; i < values.size(); ++i) {
786+ collection += " " + values[i];
787+ }
788+ return collection;
789+}
790+
791+void ClassList::add(std::string value) {
792+ values.push_back(value);
793+}
794+
795+void ClassList::remove(std::string value) {
796+ auto it_prev = std::find(values.begin(), values.end(), value);
797+ if (it_prev != values.end()) {
798+ *it_prev = values.back();
799+ values.pop_back();
800+ }
801+}
802+
803+// Constructor
804+Node::Node() : parent(nullptr) {}
805+
806+std::string Node::getTagName() const { return TagName; }
807+void Node::setTagName(const std::string& name) { TagName = name; }
808+
809+const std::unordered_map<std::string, std::string>& Node::getAttributes() const {
810+ return Attributes;
811+}
812+
813+// Implement the getter/setter methods declared using the macro
814+#define IMPLEMENT_ATTRIBUTE_ACCESSORS(_Type, _FuncNameSuffix, _AttrKeyString) \
815+ _Type Node::get##_FuncNameSuffix() const { \
816+ return getAttribute<_Type>(_AttrKeyString); \
817+ } \
818+ void Node::set##_FuncNameSuffix(_Type value) { \
819+ setAttribute(_AttrKeyString, value); \
820+ }
821+
822+IMPLEMENT_ATTRIBUTE_ACCESSORS(std::string, Id, "id")
823+IMPLEMENT_ATTRIBUTE_ACCESSORS(std::string, InnerText, "innerText")
824+IMPLEMENT_ATTRIBUTE_ACCESSORS(bool, ContentEditable, "contenteditable")
825+IMPLEMENT_ATTRIBUTE_ACCESSORS(std::string, Href, "href")
826+IMPLEMENT_ATTRIBUTE_ACCESSORS(std::string, Src, "src")
827+IMPLEMENT_ATTRIBUTE_ACCESSORS(std::string, Title, "title")
828+IMPLEMENT_ATTRIBUTE_ACCESSORS(std::string, Value, "value")
829+IMPLEMENT_ATTRIBUTE_ACCESSORS(int, TabIndex, "tabindex")
830+IMPLEMENT_ATTRIBUTE_ACCESSORS(bool, Disabled, "disabled")
831+IMPLEMENT_ATTRIBUTE_ACCESSORS(bool, Required, "required")
832+IMPLEMENT_ATTRIBUTE_ACCESSORS(bool, Checked, "checked")
833+
834+Node* Node::createElement(std::string name) {
835+ std::unique_ptr<Node> newNode = std::make_unique<Node>();
836+ newNode->setTagName(name);
837+ newNode->parent = this;
838+ children.push_back(std::move(newNode));
839+ return children.back().get();
840+}
841+
842+template<typename T>
843+void Node::setAttribute(const std::string& name, const T& value) {
844+ if constexpr (std::is_same_v<T, bool>) {
845+ if (value) {
846+ Attributes[name] = "";
847+ } else {
848+ Attributes.erase(name);
849+ }
850+ } else if constexpr (std::is_arithmetic_v<T>) {
851+ Attributes[name] = std::to_string(value);
852+ } else {
853+ static_assert(std::is_convertible_v<T, std::string>,
854+ "setAttribute: Type cannot be converted to std::string automatically.");
855+ Attributes[name] = static_cast<std::string>(value);
856+ }
857+}
858+
859+void Node::setAttribute(const std::string& name, const std::string& value) {
860+ Attributes[name] = value;
861+}
862+
863+template<typename T>
864+T Node::getAttribute(const std::string& name) const {
865+ auto it = Attributes.find(name);
866+ if (it != Attributes.end()) {
867+ const std::string& s = it->second;
868+
869+ if constexpr (std::is_same_v<T, int>) {
870+ try {
871+ return std::stoi(s);
872+ } catch (const std::invalid_argument& e) {
873+ return 0;
874+ } catch (const std::out_of_range& e) {
875+ return 0;
876+ }
877+ } else if constexpr (std::is_same_v<T, bool>) {
878+ std::string lower_s = s;
879+ std::transform(lower_s.begin(), lower_s.end(), lower_s.begin(),
880+ [](unsigned char c){ return std::tolower(c); });
881+ return (!lower_s.empty() && lower_s != "false");
882+ } else if constexpr (std::is_same_v<T, double>) {
883+ try {
884+ return std::stod(s);
885+ } catch (const std::invalid_argument& e) {
886+ return 0.0;
887+ } catch (const std::out_of_range& e) {
888+ return 0.0;
889+ }
890+ } else {
891+ static_assert(std::is_convertible_v<std::string, T>,
892+ "getAttribute: Type conversion from std::string not implemented for this type.");
893+ return static_cast<T>(s);
894+ }
895+ }
896+ return T();
897+}
898+
899+std::string Node::getAttribute(const std::string& name) const {
900+ auto it = Attributes.find(name);
901+ if (it != Attributes.end()) {
902+ return it->second;
903+ }
904+ return "";
905+}
906+
907+void Node::print(int indent) const {
908+ for (int i = 0; i < indent; ++i) {
909+ std::cout << " ";
910+ }
911+
912+ std::cout << "<" << getTagName();
913+ for (const auto& attr_pair : getAttributes()) {
914+ if (attr_pair.first == "innerText" || attr_pair.first == "tagName") {
915+ continue;
916+ }
917+ std::cout << " " << attr_pair.first;
918+ if (!attr_pair.second.empty()) {
919+ std::cout << "=\"" << attr_pair.second << "\"";
920+ }
921+ }
922+ std::cout << ">";
923+
924+ if (!getAttribute("innerText").empty()) {
925+ std::cout << "\n" << getAttribute("innerText");
926+ }
927+
928+ std::cout << std::endl;
929+
930+ for (const auto& child : children) {
931+ child->print(indent + 1);
932+ }
933+
934+ for (int i = 0; i < indent; ++i) {
935+ std::cout << " ";
936+ }
937+ std::cout << "</" << getTagName() << ">" << std::endl;
938+}
939diff --git a/events.cc b/src/events.cc
940similarity index 100%
941rename from events.cc
942rename to src/events.cc
943diff --git a/src/parser.cc b/src/parser.cc
944new file mode 100644
945index 0000000..784fbdd
946--- /dev/null
947+++ b/src/parser.cc
948@@ -0,0 +1,294 @@
949+#include "element.h"
950+#include <iostream>
951+#include <fstream>
952+#include <sstream>
953+#include <unordered_map>
954+#include <cctype>
955+#include <algorithm>
956+
957+std::unordered_map<std::string, std::string> parseAttributes(std::string token) {
958+ std::unordered_map<std::string, std::string> attrs;
959+
960+ bool inQuote = false;
961+ bool escaped = false;
962+ char quoteType = '"';
963+
964+
965+ std::string word;
966+ for (unsigned short i = 0; i < token.length(); i++) {
967+ char current = token[i];
968+ if ((current == '"' || current == '\'') && !escaped) {
969+ if (inQuote && current == quoteType) {
970+ inQuote = false;
971+ } else if (!inQuote) {
972+ inQuote = true;
973+ quoteType = current;
974+ }
975+ }
976+
977+ if (current == '\\') { //'
978+ escaped = true;
979+ continue;
980+ }
981+
982+ if ((!std::isspace(current) || inQuote) && i != token.length()-1) {
983+ word += current;
984+ } else {
985+ if (i == token.length()-1) {
986+ word += current;
987+ }
988+
989+ std::string name = "";
990+ std::string value = "";
991+ bool setValue = false;
992+
993+ for (auto c : word) {
994+ if (c == '=') {
995+ setValue = true;
996+ continue;
997+ }
998+
999+ if (setValue) {
1000+ value += c;
1001+ } else {
1002+ name += c;
1003+ }
1004+ }
1005+
1006+ word = "";
1007+
1008+ std::string trimmedName = "";
1009+ for (auto c : name) {
1010+ if (!std::isspace(c)) {
1011+ trimmedName += c;
1012+ }
1013+ }
1014+
1015+ if (attrs.size() == 0) {
1016+ value = '"'+trimmedName+'"';
1017+ trimmedName = "tagName";
1018+ }
1019+
1020+ std::string trimmedValue = "";
1021+ int sliceStart = 0;
1022+ int sliceEnd = value.length();
1023+
1024+ if (value.length() >= 2) {
1025+ for (unsigned short t = 0; t < value.length(); t++) {
1026+ if (!std::isspace(value[t])) {
1027+ sliceStart = t;
1028+ break;
1029+ }
1030+ }
1031+
1032+ for (int t = value.length()-1; t >= 0; t--) {
1033+ // Trim the trailing / on self closing elements if there isn't a space inbetween
1034+ if (!std::isspace(value[t]) && value[t] != '/') {
1035+ sliceEnd = t;
1036+ break;
1037+ }
1038+ }
1039+
1040+ // Add and subtract 1 from each side to remove quotes
1041+ for (unsigned short t = sliceStart+1; t < sliceEnd; t++) {
1042+ trimmedValue += value[t];
1043+ }
1044+ }
1045+
1046+ attrs[trimmedName] = trimmedValue;
1047+ }
1048+ }
1049+
1050+ return attrs;
1051+}
1052+
1053+std::unique_ptr<Node> parseStream(std::istream& inputStream) {
1054+ std::unique_ptr<Node> root = std::make_unique<Node>();
1055+ root->setTagName("root");
1056+
1057+ Node* currentNode = root.get();
1058+
1059+ bool inTag = false;
1060+ bool escaped = false;
1061+ bool inQuote = false;
1062+ bool inComment = false;
1063+ char quoteType = '"';
1064+
1065+ std::string token;
1066+
1067+ char current;
1068+ while (inputStream.get(current)) {
1069+
1070+ // Finds the --> and removes it then resets inComment
1071+ if (inComment) {
1072+ // added the peek to prevent hitting on every -
1073+ if (current == '-' && inputStream.peek() == '-') {
1074+ char a,b;
1075+ // load the next two
1076+ if (inputStream.get(a) && inputStream.get(b)) {
1077+ // We know b == -
1078+ if (b == '>') {
1079+ // Close the comment
1080+ inComment = false;
1081+ }
1082+ }
1083+
1084+ if (!inComment) {
1085+ // we don't put anything back and that puts us where we need to be
1086+ continue;
1087+ } else {
1088+ // Not the end
1089+ inputStream.putback(b);
1090+ inputStream.putback(a);
1091+ }
1092+ }
1093+ continue;
1094+ }
1095+
1096+ if (inTag) {
1097+ if ((current == '"' || current == '\'') && !escaped) {
1098+ if (inQuote && current == quoteType) {
1099+ inQuote = false;
1100+ } else if (!inQuote) {
1101+ inQuote = true;
1102+ quoteType = current;
1103+ }
1104+ }
1105+
1106+ if (current == '>' && !inQuote && !escaped) {
1107+ inTag = false;
1108+ // Even if the next tag is next still add a text tag as we can just check if its empty and remove it
1109+ // this check would still have to be done either way so we aren't wasting anything
1110+ if (token.length() > 0) {
1111+ bool empty = true;
1112+ bool closingTag = false;
1113+ for (int i = 0; i < token.length(); i++) {
1114+ auto c = token[i];
1115+ if (std::isspace(c)) {
1116+ continue;
1117+ } else if (c == '/') {
1118+ closingTag = true;
1119+ } else {
1120+ empty = false;
1121+ break;
1122+ }
1123+ }
1124+
1125+ bool selfClosing = false;
1126+ int selfClosingPosition = token.length()-1;
1127+ if (!closingTag) {
1128+ for (int i = token.length()-1; i >= 0; i--) {
1129+ auto c = token[i];
1130+ if (std::isspace(c)) {
1131+ continue;
1132+ } else if (c == '/') {
1133+ selfClosingPosition = i;
1134+ selfClosing = true;
1135+ break;
1136+ } else {
1137+ break;
1138+ }
1139+ }
1140+ }
1141+
1142+ if (!empty) {
1143+ if (selfClosing) {
1144+ //Create element and don't jump inside
1145+ // Use node instead of currentNode because we do not need to jump into the created element
1146+ auto attrs = parseAttributes(token.substr(0, selfClosingPosition));
1147+ auto node = currentNode->createElement(attrs["tagName"]);
1148+ for (auto const& pair : attrs) {
1149+ // All attributes are stored as strings so we can just throw them in
1150+ node->setAttribute(pair.first, pair.second);
1151+ }
1152+ } else if (closingTag) {
1153+ // Checksum and tree move
1154+ std::string tagName = "";
1155+ for (auto t : token) {
1156+ if (!std::isspace(t) && t != '/') {
1157+ tagName += t;
1158+ } else if (t == '/') {
1159+ // For closing tags we just want the name
1160+ continue;
1161+ } else if (tagName.length() > 0) {
1162+ break;
1163+ }
1164+ }
1165+ if (currentNode->getTagName() == tagName) {
1166+ currentNode = currentNode->parent;
1167+ } else {
1168+ std::cerr << "malformed html: closing tag (</" << tagName << ">) found for <" << currentNode->getTagName() << ">" << std::endl;
1169+ }
1170+ } else {
1171+ // Create a element and jump inside
1172+ auto attrs = parseAttributes(token);
1173+ currentNode = currentNode->createElement(attrs["tagName"]);
1174+ for (auto const& pair : attrs) {
1175+ // All attributes are stored as strings so we can just throw them in
1176+ currentNode->setAttribute(pair.first, pair.second);
1177+ }
1178+
1179+ }
1180+ }
1181+ }
1182+
1183+ token = "";
1184+ continue;
1185+ }
1186+ } else if (!escaped && current == '<') {
1187+ // if the next charector is a !
1188+ if (inputStream.peek() == '!') {
1189+ char a,b,c;
1190+ // load the next three charecters (includes !)
1191+ if (inputStream.get(a) && inputStream.get(b) && inputStream.get(c)) {
1192+ // We know a == !
1193+ if (b == '-' && c == '-') {
1194+ inComment = true;
1195+ }
1196+ }
1197+
1198+ if (inComment) {
1199+ continue;
1200+ } else {
1201+ // Not a comment add all back
1202+ inputStream.putback(c);
1203+ inputStream.putback(b);
1204+ inputStream.putback(a);
1205+ }
1206+ }
1207+ inTag = true;
1208+
1209+ // Heres where you actually make the text node and above the real nodes
1210+ // can also prob make the vector of tokens a single variable
1211+ // it really just needs to be a data string bc we know the type based on inTag
1212+
1213+
1214+ bool hasText = false;
1215+ for (auto t : token) {
1216+ if (!std::isspace(t)) {
1217+ hasText = true;
1218+ break;
1219+ }
1220+ }
1221+ if (hasText) {
1222+ auto node = currentNode->createElement("text");
1223+ node->setInnerText(token);
1224+ }
1225+
1226+
1227+ token = "";
1228+ continue;
1229+ }
1230+
1231+ escaped = false;
1232+
1233+ if (current == '\\') { //'
1234+ escaped = true;
1235+ continue;
1236+ }
1237+
1238+ token += current;
1239+ }
1240+
1241+ return root;
1242+}