home readme diff tree note docs

0commit d00dc89a86dd7e2fcfd4618bc3a1c8cfba9e3c3d
1Author: Mason Wright <mason@lakefox.net>
2Date:   Sun Jun 15 19:21:43 2025 -0600
3
4    Works for basic documents, does support: comments, self closing elements
5
6diff --git a/element.cc b/element.cc
7index ae1f1b4..5e0ccf6 100755
8--- a/element.cc
9+++ b/element.cc
10@@ -8,6 +8,21 @@
11 #include <algorithm>
12 #include <stdexcept>
13 
14+
15+// --- THE MACRO DEFINITION ---
16+// This macro generates a getter and a setter for a specific attribute.
17+// _Type: The C++ data type (e.g., std::string, int, bool)
18+// _FuncNameSuffix: The PascalCase suffix for the getter/setter (e.g., Id, TabIndex)
19+// _AttrKeyString: The exact string literal key used in the Attributes map (e.g., "id", "tabindex")
20+#define GENERATE_ATTRIBUTE_ACCESSORS(_Type, _FuncNameSuffix, _AttrKeyString) \
21+    _Type get##_FuncNameSuffix() const { \
22+        return getAttribute<_Type>(_AttrKeyString); \
23+    } \
24+    void set##_FuncNameSuffix(_Type value) { \
25+        setAttribute(_AttrKeyString, value); \
26+    }
27+
28+
29 struct Styles {
30 	std::vector<std::unordered_map<std::string, std::string>> stylesheets;
31 	std::unordered_map<std::string, std::string> inlineStyles;
32@@ -65,52 +80,36 @@ class Node {
33 		std::string TagName;
34 		// !NOTE: ContentEditable only supports plaintext
35 		std::unordered_map<std::string, std::string> Attributes;
36-
37-		template<typename T>
38-			T stringToType(const std::string& s) const {
39-				// This should theoretically not be called if we have std::string overload
40-				// but acts as a fallback for unsupported types or as a reminder.
41-				static_assert(!std::is_same<T, T>::value, "stringToType not implemented for this type");
42-				return T(); // Should not reach here
43-			}
44-
45-		int stringToType(const std::string& s) const {
46-			try {
47-				return std::stoi(s);
48-			} catch (const std::invalid_argument& e) {
49-				std::cerr << "Warning: Invalid integer attribute value '" << s << "'. Defaulting to 0." << std::endl;
50-				return 0;
51-			} catch (const std::out_of_range& e) {
52-				std::cerr << "Warning: Integer attribute value '" << s << "' out of range. Defaulting to 0." << std::endl;
53-				return 0;
54-			}
55-		}
56-
57-		bool stringToType(const std::string& s) const {
58-			// For boolean attributes, presence means true, value might be "true" or ""
59-			// You decide the logic. Common HTML: attribute presence means true.
60-			// If your parser puts "false" in the map for contenteditable="false", then check "false"
61-			// If it puts "" for contenteditable, check for non-empty string.
62-			// My setAttribute for bool puts "" for true, and removes for false.
63-			// So, if the attribute is present, it's true.
64-			return true; // If attribute exists, it's considered true
65-				     // If you want to strictly parse "true"/"false" strings:
66-				     // return (s == "" || s == "true"); // Assuming empty string means true for boolean attribute
67-				     // return (s != "false" && !s.empty()); // If "false" explicitly means false
68-		}
69-
70-		// Add more overloads for other types like double, float, long, etc.
71-		double stringToType(const std::string& s) const {
72-			try {
73-				return std::stod(s);
74-			} catch (...) { /* error handling */ return 0.0; }
75-		}
76 	public:
77 		Node* parent;
78 		std::vector<std::unique_ptr<Node>> children;
79 		ClassList classList;
80 
81 		Node() : parent(nullptr) {}
82+		// Can't use macro on tag name
83+		std::string getTagName() const { return TagName; }
84+		void setTagName(const std::string& name) { TagName = name; }
85+
86+		const std::unordered_map<std::string, std::string>& getAttributes() const {
87+			return Attributes;
88+		}
89+
90+
91+		// --- Define Getters and Setters
92+		// !TODO: Add all global attributes
93+		// + Src: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes
94+		GENERATE_ATTRIBUTE_ACCESSORS(std::string, Id, "id")
95+		GENERATE_ATTRIBUTE_ACCESSORS(std::string, InnerText, "innerText") // If you want innerText as an attribute
96+		GENERATE_ATTRIBUTE_ACCESSORS(bool, ContentEditable, "contenteditable")
97+		GENERATE_ATTRIBUTE_ACCESSORS(std::string, Href, "href")
98+		GENERATE_ATTRIBUTE_ACCESSORS(std::string, Src, "src")
99+		GENERATE_ATTRIBUTE_ACCESSORS(std::string, Title, "title")
100+		GENERATE_ATTRIBUTE_ACCESSORS(std::string, Value, "value")
101+		GENERATE_ATTRIBUTE_ACCESSORS(int, TabIndex, "tabindex")
102+		GENERATE_ATTRIBUTE_ACCESSORS(bool, Disabled, "disabled")
103+		GENERATE_ATTRIBUTE_ACCESSORS(bool, Required, "required")
104+		GENERATE_ATTRIBUTE_ACCESSORS(bool, Checked, "checked")
105+
106 
107 		Node* createElement(std::string name) {
108 			std::unique_ptr<Node> newNode = std::make_unique<Node>();
109@@ -121,45 +120,84 @@ class Node {
110 		}
111 
112 
113-		// --- Templated setAttribute ---
114-		// This will convert the given value (of any type T) to std::string
115+		// --- Generic setAttribute (Type to string conversion) ---
116 		template<typename T>
117 			void setAttribute(const std::string& name, const T& value) {
118-				Attributes[name] = std::to_string(value); // Default for numbers, bools need custom
119+				if constexpr (std::is_same_v<T, bool>) {
120+					if (value) {
121+						Attributes[name] = "";
122+					} else {
123+						Attributes.erase(name);
124+					}
125+				} else if constexpr (std::is_arithmetic_v<T>) { // Handles int, double, etc.
126+					Attributes[name] = std::to_string(value);
127+				} else {
128+					// This static_assert will fire if you try to use setAttribute with a type
129+					// that doesn't have a specific handling or a std::string overload.
130+					static_assert(std::is_convertible_v<T, std::string>,
131+							"setAttribute: Type cannot be converted to std::string automatically.");
132+					// If it's convertible, let's try to convert it.
133+					Attributes[name] = static_cast<std::string>(value);
134+				}
135 			}
136 
137-		// Overload for std::string to avoid unnecessary conversion
138+		// Overload for std::string to avoid unnecessary conversion (and explicit to_string)
139 		void setAttribute(const std::string& name, const std::string& value) {
140 			Attributes[name] = value;
141 		}
142 
143-		// Overload for bool (custom serialization)
144-		void setAttribute(const std::string& name, bool value) {
145-			Attributes[name] = value ? "" : "false"; 
146-			if (value) {
147-				Attributes[name] = ""; // Attribute present with empty value typically means 'true'
148-			} else {
149-				// If value doesn't exist then remove it
150-				Attributes.erase(name);
151-			}
152-		}
153-
154 
155-		// --- Templated getAttribute ---
156-		// This will convert the string from the map to the desired type T
157+		// --- Generic getAttribute (string to Type conversion) ---
158 		template<typename T>
159 			T getAttribute(const std::string& name) const {
160 				auto it = Attributes.find(name);
161 				if (it != Attributes.end()) {
162-					// Need to convert it->second (std::string) to T
163-					// This requires a helper or std::from_chars/stoi/etc.
164-					return stringToType<T>(it->second);
165+					const std::string& s = it->second; // Get the string value from the map
166+
167+					// Use if constexpr to convert the string to the requested type T
168+					if constexpr (std::is_same_v<T, int>) {
169+						try {
170+							return std::stoi(s);
171+						} catch (const std::invalid_argument& e) {
172+							std::cerr << "Warning: Invalid integer attribute value '" << s << "' for '" << name << "'. Defaulting to 0. " << e.what() << std::endl;
173+							return 0;
174+						} catch (const std::out_of_range& e) {
175+							std::cerr << "Warning: Integer attribute value '" << s << "' for '" << name << "' out of range. Defaulting to 0. " << e.what() << std::endl;
176+							return 0;
177+						}
178+					} else if constexpr (std::is_same_v<T, bool>) {
179+						// Return true if string is not empty and not "false" (case-insensitive)
180+						// Adjust this logic if you have different boolean attribute parsing rules
181+						std::string lower_s = s;
182+						std::transform(lower_s.begin(), lower_s.end(), lower_s.begin(),
183+								[](unsigned char c){ return std::tolower(c); });
184+						return (!lower_s.empty() && lower_s != "false");
185+					} else if constexpr (std::is_same_v<T, double>) {
186+						try {
187+							return std::stod(s);
188+						} catch (const std::invalid_argument& e) {
189+							std::cerr << "Warning: Invalid double attribute value '" << s << "' for '" << name << "'. Defaulting to 0.0. " << e.what() << std::endl;
190+							return 0.0;
191+						} catch (const std::out_of_range& e) {
192+							std::cerr << "Warning: Double attribute value '" << s << "' for '" << name << "' out of range. Defaulting to 0.0. " << e.what() << std::endl;
193+							return 0.0;
194+						}
195+					} else {
196+						// If a type is requested that isn't explicitly handled,
197+						// this static_assert will cause a compile error.
198+						static_assert(std::is_convertible_v<std::string, T>,
199+								"getAttribute: Type conversion from std::string not implemented for this type.");
200+						// If it's convertible, attempt a static_cast (might not be what you want for all types)
201+						return static_cast<T>(s);
202+					}
203 				}
204-				// Fail return default constructor for T
205+				// Return a default-constructed T if attribute not found
206+				// This relies on T having a default constructor (e.g., int() is 0, bool() is false, std::string() is empty)
207 				return T();
208 			}
209 
210-		// Overload for std::string directly
211+		// Overload for std::string directly (avoids template instantiation and conversion overhead)
212+		// This function will be preferred by the compiler when T is std::string.
213 		std::string getAttribute(const std::string& name) const {
214 			auto it = Attributes.find(name);
215 			if (it != Attributes.end()) {
216@@ -167,6 +205,50 @@ class Node {
217 			}
218 			return ""; // Default for string attributes if not found
219 		}
220+
221+		void print(int indent = 0) const {
222+			// Print indentation
223+			for (int i = 0; i < indent; ++i) {
224+				std::cout << "  ";
225+			}
226+
227+			// Print node information
228+			std::cout << "<" << getTagName();
229+
230+			// Print attributes
231+			for (const auto& attr_pair : getAttributes()) {
232+				if (attr_pair.first == "innerText") {
233+					continue;
234+				}
235+				std::cout << " " << attr_pair.first;
236+				if (!attr_pair.second.empty()) { // Only print value if it's not empty (for boolean attributes)
237+					std::cout << "=\"" << attr_pair.second << "\"";
238+				}
239+			}
240+			std::cout << ">";
241+
242+			// Print inner text if any
243+			// Note: HTML whitespace rules are complex; this just prints it raw
244+			if (!getInnerText().empty()) {
245+				std::cout << "\n" << getInnerText();
246+			}
247+
248+			std::cout << std::endl;
249+
250+			// Recursively call print for children
251+			for (const auto& child : children) {
252+				child->print(indent + 1); // Increase indent for children
253+			}
254+
255+			// Print closing tag if it's not a self-closing/root tag
256+			// (Assuming you'll refine logic for self-closing tags eventually)
257+			if (!children.empty() || !getInnerText().empty()) {
258+				for (int i = 0; i < indent; ++i) {
259+					std::cout << "  ";
260+				}
261+				std::cout << "</" << getTagName() << ">" << std::endl;
262+			}
263+		}
264 };
265 
266 std::unordered_map<std::string, std::string> parseAttributes(std::vector<std::string> tokens) {
267@@ -255,6 +337,7 @@ std::unique_ptr<Node> parserdocument(std::string file) {
268 	std::vector<std::string> tokens;
269 	std::string word = "";
270 
271+	// !TODO: self closing tags <br> <br/> <input />
272 	while (std::getline(inputFile, line)) {
273 		int ll = line.length();
274 		for (unsigned short i = 0; i < ll; i++) {
275@@ -263,12 +346,29 @@ std::unique_ptr<Node> parserdocument(std::string file) {
276 			if (current == '/' && inTag) {
277 				currentNode = currentNode->parent;
278 				inClosing = true;
279+				word = "";
280+				inTag = false;
281+				tokens.clear();
282 			}
283 
284 			// Hijack the closing skipping to skipp over comments
285 			if (current == '!' && inTag) {
286 				inClosing = true;
287 			}
288+
289+			// Create the element outside of any tag parts
290+			if (!inTag && tokens.size() > 0 && !inClosing && current != '<') {
291+				// Build the element 
292+				currentNode = currentNode->createElement(tokens[0]);
293+				auto attrs = parseAttributes(tokens);
294+				for (auto const& pair : attrs) {
295+					// All attributes are stored as strings so we can just throw them in
296+					currentNode->setAttribute<std::string>(pair.first, pair.second);
297+				}
298+
299+				tokens.clear();
300+				word = "";
301+			}
302 			
303 			if (current == '>') {
304 				tokens.push_back(word);
305@@ -297,7 +397,7 @@ std::unique_ptr<Node> parserdocument(std::string file) {
306 				} else {
307 					word += current;
308 				}
309-			} else if (current != '<' || current != '>') {
310+			} else if (current != '<' && current != '>') {
311 				// Outside tag add to innerText
312 				currentNode->setInnerText(currentNode->getInnerText() + current);
313 			}
314@@ -312,72 +412,20 @@ std::unique_ptr<Node> parserdocument(std::string file) {
315 				escaped = true;
316 			}
317 
318-			if (!inTag && tokens.size() > 0) {
319-				// Build the element 
320-				currentNode = currentNode->createElement(tokens[0]);
321-				auto attrs = parseAttributes(tokens);
322-				for (auto const& pair : attrs) {
323-					auto v = pair.second;
324-					// !TODO: Add all global attributes
325-					// + Src: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes
326-					if(pair.first == "id") {
327-						currentNode->setId(v);
328-					} else if (pair.first == "contentEditable") {
329-						if (v != "false") {
330-						}
331-						currentNode->setContentEditable(true);
332-					} else if (pair.first == "href") {
333-						currentNode->setHref(v);
334-					} else if (pair.first == "src") {
335-						currentNode->setSrc(v);
336-					} else if (pair.first == "title") {
337-						currentNode->setTitle(v);
338-					} else if (pair.first == "value") {
339-						currentNode->setValue(v);
340-					} else if (pair.first == "tabindex") {
341-						try {
342-							int tabindex = std::stoi(v);
343-							currentNode->setTabIndex(tabindex);
344-						} catch (const std::invalid_argument& e) {
345-							std::cerr << "Error converting \"" << v << "\": Invalid argument: \"tabindex\"=" << e.what() << std::endl;
346-						} catch (const std::out_of_range& e) {
347-							std::cerr << "Error converting \"" << v << "\": Out of range: \"tabindex\"=" << e.what() << std::endl;
348-						}
349-					} else if (pair.first == "disabled") {
350-						if (v != "false") {
351-							currentNode->setDisabled(true);
352-						}
353-					} else if (pair.first == "checked") {
354-						if (v != "false") {
355-							currentNode->setChecked(true);
356-						}
357-					} else if (pair.first == "required") {
358-						if (v != "false") {
359-							currentNode->setRequired(true);
360-						}
361-					} else {
362-						currentNode->setAttribute(pair.first, v);
363-					}
364-				}
365-
366-				tokens.clear();
367-				word = "";
368-			}
369+			
370 		}
371 	}
372 
373 	inputFile.close();
374 
375-
376-	std::cout << root->children.size() << std::endl;
377-	std::cout << root->children[0].get()->children[1].get()->getTagName() << std::endl;
378-
379 	return root;
380 }
381 
382 
383 int main() {
384 
385-	parserdocument("./index.html");
386+	auto document = parserdocument("./index.html");
387+
388+	document->print();
389 	return 0;
390 }
391diff --git a/index.html b/index.html
392index 1e8f67f..2f0e2ac 100644
393--- a/index.html
394+++ b/index.html
395@@ -3,7 +3,7 @@
396 	contentEditable
397 >
398 	hi
399-	<p>
400+	<p id="test">
401 		this is a test
402 	</p>
403 </html>
404diff --git a/main b/main
405index 38a69b3..ac8b53e 100755
406Binary files a/main and b/main differ