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