Author: Mason Wright
Email:
[email protected]
Date: Sun, 15 Jun 2025 19:21:43 -0600
element.cc
d00dc89a86dd7e2fcfd4618bc3a1c8cfba9e3c3d
Works for basic documents, does support: comments, self closing elements
Clone
-
adapter.cc
-
build.sh
M
element.cc
-
events.cc
M
index.html
M
main
-
main.cc
Commits
b966b2a517365074e5c381dbdea05b3221dc0198
e840f1eeb0ae26af69e1ae146ea9938e28e9f1af
e4e05418a640eaed08cd1ec7cd8644eb1dbcca50
4e01ba8ad2c3361fa4be3d896288020948b58b5e
aae562ac1350480e4889aabb35899f776c5b59e9
6c3ae0e31eb0893f20e3872117f92cc6b9a942af
350e7d88bb2feb9db00c6e032cc6623f215b7adf
95e6c70d23e99ffcf70e5bbe12503496e5d8f232
e188783659b9bc3b9993a647e93ed110e7f41db6
5e4c38ff3c212cdd9881427ef3f8c2706539a190
e50ea9e1356a74af18fdd171337ef9dc931e1f4e
8f2e83556d12aaebe8e8597ea6923804b0eb7a43
1627c585128af263181053ab2cf1a4cdcd14ee21
def3513f75b325464ad88a33c741c4ca80572b77
a21501590980a905fa9b902897d700a42a08b7f0
56074a6bfe4498d092f3a227297c8c20e2bb962c
d9cf1485b7ae0614130494f0e73237921323b9a1
80f04b134ae32ad8a9d526007b33dd02f6600f05
23d6c65f9368d3c622a55a3068a6b2f1efa0c8d4
09c195df02536b6a796bd648fce9669397b96109
f2b5c8202fbc904e2ed78260e3fdbd55164799d2
4bfba076120f389994fc46a98e8b7a2622314400
e36ac5417e10ee9b9f94f340e1ccf28afc5705ea
d00dc89a86dd7e2fcfd4618bc3a1c8cfba9e3c3d
d9eef16adaf292f3748db5fb5aa98463de10d712
18ff2ec1bfc1cf9fcd17c1acb05c3b41f8f0ed83
9e7fd2980d723437ea621b78d395fa72ca3f4922
Diff
diff --git a/element.cc b/element.cc index 5e0ccf6..ae1f1b4 100755 --- a/element.cc +++ b/element.cc @@ -11,15 +10,0 @@ - -// --- THE MACRO DEFINITION --- -// This macro generates a getter and a setter for a specific attribute. -// _Type: The C++ data type (e.g., std::string, int, bool) -// _FuncNameSuffix: The PascalCase suffix for the getter/setter (e.g., Id, TabIndex) -// _AttrKeyString: The exact string literal key used in the Attributes map (e.g., "id", "tabindex") -#define GENERATE_ATTRIBUTE_ACCESSORS(_Type, _FuncNameSuffix, _AttrKeyString) \ - _Type get##_FuncNameSuffix() const { \ - return getAttribute<_Type>(_AttrKeyString); \ - } \ - void set##_FuncNameSuffix(_Type value) { \ - setAttribute(_AttrKeyString, value); \ - } - - @@ -83,4 +67,0 @@ class Node { - public: - Node* parent; - std::vector
> children; - ClassList classList; @@ -88,4 +69,7 @@ class Node { - Node() : parent(nullptr) {} - // Can't use macro on tag name - std::string getTagName() const { return TagName; } - void setTagName(const std::string& name) { TagName = name; } + template
+ T stringToType(const std::string& s) const { + // This should theoretically not be called if we have std::string overload + // but acts as a fallback for unsupported types or as a reminder. + static_assert(!std::is_same
::value, "stringToType not implemented for this type"); + return T(); // Should not reach here + } @@ -93,2 +77,10 @@ class Node { - const std::unordered_map
& getAttributes() const { - return Attributes; + int stringToType(const std::string& s) const { + try { + return std::stoi(s); + } catch (const std::invalid_argument& e) { + std::cerr << "Warning: Invalid integer attribute value '" << s << "'. Defaulting to 0." << std::endl; + return 0; + } catch (const std::out_of_range& e) { + std::cerr << "Warning: Integer attribute value '" << s << "' out of range. Defaulting to 0." << std::endl; + return 0; + } @@ -96,0 +89,12 @@ class Node { + bool stringToType(const std::string& s) const { + // For boolean attributes, presence means true, value might be "true" or "" + // You decide the logic. Common HTML: attribute presence means true. + // If your parser puts "false" in the map for contenteditable="false", then check "false" + // If it puts "" for contenteditable, check for non-empty string. + // My setAttribute for bool puts "" for true, and removes for false. + // So, if the attribute is present, it's true. + return true; // If attribute exists, it's considered true + // If you want to strictly parse "true"/"false" strings: + // return (s == "" || s == "true"); // Assuming empty string means true for boolean attribute + // return (s != "false" && !s.empty()); // If "false" explicitly means false + } @@ -98,14 +102,10 @@ class Node { - // --- Define Getters and Setters - // !TODO: Add all global attributes - // + Src: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes - GENERATE_ATTRIBUTE_ACCESSORS(std::string, Id, "id") - GENERATE_ATTRIBUTE_ACCESSORS(std::string, InnerText, "innerText") // If you want innerText as an attribute - GENERATE_ATTRIBUTE_ACCESSORS(bool, ContentEditable, "contenteditable") - GENERATE_ATTRIBUTE_ACCESSORS(std::string, Href, "href") - GENERATE_ATTRIBUTE_ACCESSORS(std::string, Src, "src") - GENERATE_ATTRIBUTE_ACCESSORS(std::string, Title, "title") - GENERATE_ATTRIBUTE_ACCESSORS(std::string, Value, "value") - GENERATE_ATTRIBUTE_ACCESSORS(int, TabIndex, "tabindex") - GENERATE_ATTRIBUTE_ACCESSORS(bool, Disabled, "disabled") - GENERATE_ATTRIBUTE_ACCESSORS(bool, Required, "required") - GENERATE_ATTRIBUTE_ACCESSORS(bool, Checked, "checked") + // Add more overloads for other types like double, float, long, etc. + double stringToType(const std::string& s) const { + try { + return std::stod(s); + } catch (...) { /* error handling */ return 0.0; } + } + public: + Node* parent; + std::vector
> children; + ClassList classList; @@ -112,0 +113 @@ class Node { + Node() : parent(nullptr) {} @@ -123 +124,2 @@ class Node { - // --- Generic setAttribute (Type to string conversion) --- + // --- Templated setAttribute --- + // This will convert the given value (of any type T) to std::string @@ -126,16 +128 @@ class Node { - if constexpr (std::is_same_v
) { - if (value) { - Attributes[name] = ""; - } else { - Attributes.erase(name); - } - } else if constexpr (std::is_arithmetic_v
) { // Handles int, double, etc. - Attributes[name] = std::to_string(value); - } else { - // This static_assert will fire if you try to use setAttribute with a type - // that doesn't have a specific handling or a std::string overload. - static_assert(std::is_convertible_v
, - "setAttribute: Type cannot be converted to std::string automatically."); - // If it's convertible, let's try to convert it. - Attributes[name] = static_cast
(value); - } + Attributes[name] = std::to_string(value); // Default for numbers, bools need custom @@ -144 +131 @@ class Node { - // Overload for std::string to avoid unnecessary conversion (and explicit to_string) + // Overload for std::string to avoid unnecessary conversion @@ -148,0 +136,11 @@ class Node { + // Overload for bool (custom serialization) + void setAttribute(const std::string& name, bool value) { + Attributes[name] = value ? "" : "false"; + if (value) { + Attributes[name] = ""; // Attribute present with empty value typically means 'true' + } else { + // If value doesn't exist then remove it + Attributes.erase(name); + } + } + @@ -150 +148,2 @@ class Node { - // --- Generic getAttribute (string to Type conversion) --- + // --- Templated getAttribute --- + // This will convert the string from the map to the desired type T @@ -155,38 +154,3 @@ class Node { - const std::string& s = it->second; // Get the string value from the map - - // Use if constexpr to convert the string to the requested type T - if constexpr (std::is_same_v
) { - try { - return std::stoi(s); - } catch (const std::invalid_argument& e) { - std::cerr << "Warning: Invalid integer attribute value '" << s << "' for '" << name << "'. Defaulting to 0. " << e.what() << std::endl; - return 0; - } catch (const std::out_of_range& e) { - std::cerr << "Warning: Integer attribute value '" << s << "' for '" << name << "' out of range. Defaulting to 0. " << e.what() << std::endl; - return 0; - } - } else if constexpr (std::is_same_v
) { - // Return true if string is not empty and not "false" (case-insensitive) - // Adjust this logic if you have different boolean attribute parsing rules - std::string lower_s = s; - std::transform(lower_s.begin(), lower_s.end(), lower_s.begin(), - [](unsigned char c){ return std::tolower(c); }); - return (!lower_s.empty() && lower_s != "false"); - } else if constexpr (std::is_same_v
) { - try { - return std::stod(s); - } catch (const std::invalid_argument& e) { - std::cerr << "Warning: Invalid double attribute value '" << s << "' for '" << name << "'. Defaulting to 0.0. " << e.what() << std::endl; - return 0.0; - } catch (const std::out_of_range& e) { - std::cerr << "Warning: Double attribute value '" << s << "' for '" << name << "' out of range. Defaulting to 0.0. " << e.what() << std::endl; - return 0.0; - } - } else { - // If a type is requested that isn't explicitly handled, - // this static_assert will cause a compile error. - static_assert(std::is_convertible_v
, - "getAttribute: Type conversion from std::string not implemented for this type."); - // If it's convertible, attempt a static_cast (might not be what you want for all types) - return static_cast
(s); - } + // Need to convert it->second (std::string) to T + // This requires a helper or std::from_chars/stoi/etc. + return stringToType
(it->second); @@ -194,2 +158 @@ class Node { - // Return a default-constructed T if attribute not found - // This relies on T having a default constructor (e.g., int() is 0, bool() is false, std::string() is empty) + // Fail return default constructor for T @@ -199,2 +162 @@ class Node { - // Overload for std::string directly (avoids template instantiation and conversion overhead) - // This function will be preferred by the compiler when T is std::string. + // Overload for std::string directly @@ -208,44 +169,0 @@ class Node { - - void print(int indent = 0) const { - // Print indentation - for (int i = 0; i < indent; ++i) { - std::cout << " "; - } - - // Print node information - std::cout << "<" << getTagName(); - - // Print attributes - for (const auto& attr_pair : getAttributes()) { - if (attr_pair.first == "innerText") { - continue; - } - std::cout << " " << attr_pair.first; - if (!attr_pair.second.empty()) { // Only print value if it's not empty (for boolean attributes) - std::cout << "=\"" << attr_pair.second << "\""; - } - } - std::cout << ">"; - - // Print inner text if any - // Note: HTML whitespace rules are complex; this just prints it raw - if (!getInnerText().empty()) { - std::cout << "\n" << getInnerText(); - } - - std::cout << std::endl; - - // Recursively call print for children - for (const auto& child : children) { - child->print(indent + 1); // Increase indent for children - } - - // Print closing tag if it's not a self-closing/root tag - // (Assuming you'll refine logic for self-closing tags eventually) - if (!children.empty() || !getInnerText().empty()) { - for (int i = 0; i < indent; ++i) { - std::cout << " "; - } - std::cout << "" << getTagName() << ">" << std::endl; - } - } @@ -340 +257,0 @@ std::unique_ptr
parserdocument(std::string file) { - // !TODO: self closing tags
@@ -349,3 +265,0 @@ std::unique_ptr
parserdocument(std::string file) { - word = ""; - inTag = false; - tokens.clear(); @@ -358,14 +271,0 @@ std::unique_ptr
parserdocument(std::string file) { - - // Create the element outside of any tag parts - if (!inTag && tokens.size() > 0 && !inClosing && current != '<') { - // Build the element - currentNode = currentNode->createElement(tokens[0]); - auto attrs = parseAttributes(tokens); - for (auto const& pair : attrs) { - // All attributes are stored as strings so we can just throw them in - currentNode->setAttribute
(pair.first, pair.second); - } - - tokens.clear(); - word = ""; - } @@ -400 +300 @@ std::unique_ptr
parserdocument(std::string file) { - } else if (current != '<' && current != '>') { + } else if (current != '<' || current != '>') { @@ -415 +315,51 @@ std::unique_ptr
parserdocument(std::string file) { - + if (!inTag && tokens.size() > 0) { + // Build the element + currentNode = currentNode->createElement(tokens[0]); + auto attrs = parseAttributes(tokens); + for (auto const& pair : attrs) { + auto v = pair.second; + // !TODO: Add all global attributes + // + Src: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes + if(pair.first == "id") { + currentNode->setId(v); + } else if (pair.first == "contentEditable") { + if (v != "false") { + } + currentNode->setContentEditable(true); + } else if (pair.first == "href") { + currentNode->setHref(v); + } else if (pair.first == "src") { + currentNode->setSrc(v); + } else if (pair.first == "title") { + currentNode->setTitle(v); + } else if (pair.first == "value") { + currentNode->setValue(v); + } else if (pair.first == "tabindex") { + try { + int tabindex = std::stoi(v); + currentNode->setTabIndex(tabindex); + } catch (const std::invalid_argument& e) { + std::cerr << "Error converting \"" << v << "\": Invalid argument: \"tabindex\"=" << e.what() << std::endl; + } catch (const std::out_of_range& e) { + std::cerr << "Error converting \"" << v << "\": Out of range: \"tabindex\"=" << e.what() << std::endl; + } + } else if (pair.first == "disabled") { + if (v != "false") { + currentNode->setDisabled(true); + } + } else if (pair.first == "checked") { + if (v != "false") { + currentNode->setChecked(true); + } + } else if (pair.first == "required") { + if (v != "false") { + currentNode->setRequired(true); + } + } else { + currentNode->setAttribute(pair.first, v); + } + } + + tokens.clear(); + word = ""; + } @@ -420,0 +371,4 @@ std::unique_ptr
parserdocument(std::string file) { + + std::cout << root->children.size() << std::endl; + std::cout << root->children[0].get()->children[1].get()->getTagName() << std::endl; + @@ -427,3 +381 @@ int main() { - auto document = parserdocument("./index.html"); - - document->print(); + parserdocument("./index.html");
#include
#include
#include
#include
#include
#include
#include
#include
#include
// --- THE MACRO DEFINITION --- // This macro generates a getter and a setter for a specific attribute. // _Type: The C++ data type (e.g., std::string, int, bool) // _FuncNameSuffix: The PascalCase suffix for the getter/setter (e.g., Id, TabIndex) // _AttrKeyString: The exact string literal key used in the Attributes map (e.g., "id", "tabindex") #define GENERATE_ATTRIBUTE_ACCESSORS(_Type, _FuncNameSuffix, _AttrKeyString) \ _Type get##_FuncNameSuffix() const { \ return getAttribute<_Type>(_AttrKeyString); \ } \ void set##_FuncNameSuffix(_Type value) { \ setAttribute(_AttrKeyString, value); \ } struct Styles { std::vector
> stylesheets; std::unordered_map
inlineStyles; std::unordered_map
> psuedoStyles; }; struct Bounds { int top; int right; int bottom; int left; }; struct State { // Bounds offset; // Border border; // std::vector
background; int width; int height; int z; bool hidden; int tabIndex; }; class ClassList { private: std::vector
values; public: std::string value() const { if (values.empty()) { return ""; } std::string collection = values[0]; for (size_t i = 1; i < values.size(); ++i) { collection += " " + values[i]; } return collection; } void add(std::string value) { values.push_back(value); } void remove(std::string value) { auto it_prev = std::find(values.begin(), values.end(), value); if (it_prev != values.end()) { *it_prev = values.back(); // Overwrite prevous position with last element values.pop_back(); // Remove the last element } } }; class Node { private: std::string TagName; // !NOTE: ContentEditable only supports plaintext std::unordered_map
Attributes; public: Node* parent; std::vector
> children; ClassList classList; Node() : parent(nullptr) {} // Can't use macro on tag name std::string getTagName() const { return TagName; } void setTagName(const std::string& name) { TagName = name; } const std::unordered_map
& getAttributes() const { return Attributes; } // --- Define Getters and Setters // !TODO: Add all global attributes // + Src: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes GENERATE_ATTRIBUTE_ACCESSORS(std::string, Id, "id") GENERATE_ATTRIBUTE_ACCESSORS(std::string, InnerText, "innerText") // If you want innerText as an attribute GENERATE_ATTRIBUTE_ACCESSORS(bool, ContentEditable, "contenteditable") GENERATE_ATTRIBUTE_ACCESSORS(std::string, Href, "href") GENERATE_ATTRIBUTE_ACCESSORS(std::string, Src, "src") GENERATE_ATTRIBUTE_ACCESSORS(std::string, Title, "title") GENERATE_ATTRIBUTE_ACCESSORS(std::string, Value, "value") GENERATE_ATTRIBUTE_ACCESSORS(int, TabIndex, "tabindex") GENERATE_ATTRIBUTE_ACCESSORS(bool, Disabled, "disabled") GENERATE_ATTRIBUTE_ACCESSORS(bool, Required, "required") GENERATE_ATTRIBUTE_ACCESSORS(bool, Checked, "checked") Node* createElement(std::string name) { std::unique_ptr
newNode = std::make_unique
(); newNode->setTagName(name); newNode->parent = this; children.push_back(std::move(newNode)); return children.back().get(); } // --- Generic setAttribute (Type to string conversion) --- template
void setAttribute(const std::string& name, const T& value) { if constexpr (std::is_same_v
) { if (value) { Attributes[name] = ""; } else { Attributes.erase(name); } } else if constexpr (std::is_arithmetic_v
) { // Handles int, double, etc. Attributes[name] = std::to_string(value); } else { // This static_assert will fire if you try to use setAttribute with a type // that doesn't have a specific handling or a std::string overload. static_assert(std::is_convertible_v
, "setAttribute: Type cannot be converted to std::string automatically."); // If it's convertible, let's try to convert it. Attributes[name] = static_cast
(value); } } // Overload for std::string to avoid unnecessary conversion (and explicit to_string) void setAttribute(const std::string& name, const std::string& value) { Attributes[name] = value; } // --- Generic getAttribute (string to Type conversion) --- template
T getAttribute(const std::string& name) const { auto it = Attributes.find(name); if (it != Attributes.end()) { const std::string& s = it->second; // Get the string value from the map // Use if constexpr to convert the string to the requested type T if constexpr (std::is_same_v
) { try { return std::stoi(s); } catch (const std::invalid_argument& e) { std::cerr << "Warning: Invalid integer attribute value '" << s << "' for '" << name << "'. Defaulting to 0. " << e.what() << std::endl; return 0; } catch (const std::out_of_range& e) { std::cerr << "Warning: Integer attribute value '" << s << "' for '" << name << "' out of range. Defaulting to 0. " << e.what() << std::endl; return 0; } } else if constexpr (std::is_same_v
) { // Return true if string is not empty and not "false" (case-insensitive) // Adjust this logic if you have different boolean attribute parsing rules std::string lower_s = s; std::transform(lower_s.begin(), lower_s.end(), lower_s.begin(), [](unsigned char c){ return std::tolower(c); }); return (!lower_s.empty() && lower_s != "false"); } else if constexpr (std::is_same_v
) { try { return std::stod(s); } catch (const std::invalid_argument& e) { std::cerr << "Warning: Invalid double attribute value '" << s << "' for '" << name << "'. Defaulting to 0.0. " << e.what() << std::endl; return 0.0; } catch (const std::out_of_range& e) { std::cerr << "Warning: Double attribute value '" << s << "' for '" << name << "' out of range. Defaulting to 0.0. " << e.what() << std::endl; return 0.0; } } else { // If a type is requested that isn't explicitly handled, // this static_assert will cause a compile error. static_assert(std::is_convertible_v
, "getAttribute: Type conversion from std::string not implemented for this type."); // If it's convertible, attempt a static_cast (might not be what you want for all types) return static_cast
(s); } } // Return a default-constructed T if attribute not found // This relies on T having a default constructor (e.g., int() is 0, bool() is false, std::string() is empty) return T(); } // Overload for std::string directly (avoids template instantiation and conversion overhead) // This function will be preferred by the compiler when T is std::string. std::string getAttribute(const std::string& name) const { auto it = Attributes.find(name); if (it != Attributes.end()) { return it->second; } return ""; // Default for string attributes if not found } void print(int indent = 0) const { // Print indentation for (int i = 0; i < indent; ++i) { std::cout << " "; } // Print node information std::cout << "<" << getTagName(); // Print attributes for (const auto& attr_pair : getAttributes()) { if (attr_pair.first == "innerText") { continue; } std::cout << " " << attr_pair.first; if (!attr_pair.second.empty()) { // Only print value if it's not empty (for boolean attributes) std::cout << "=\"" << attr_pair.second << "\""; } } std::cout << ">"; // Print inner text if any // Note: HTML whitespace rules are complex; this just prints it raw if (!getInnerText().empty()) { std::cout << "\n" << getInnerText(); } std::cout << std::endl; // Recursively call print for children for (const auto& child : children) { child->print(indent + 1); // Increase indent for children } // Print closing tag if it's not a self-closing/root tag // (Assuming you'll refine logic for self-closing tags eventually) if (!children.empty() || !getInnerText().empty()) { for (int i = 0; i < indent; ++i) { std::cout << " "; } std::cout << "" << getTagName() << ">" << std::endl; } } }; std::unordered_map
parseAttributes(std::vector
tokens) { std::unordered_map
attrs; for (unsigned short i = 0; i < tokens.size(); i++) { if (i != 0) { std::string name = ""; std::string value = ""; bool setValue = false; for (auto c : tokens[i]) { if (c == '=') { setValue = true; continue; } if (setValue) { value += c; } else { name += c; } } std::string trimmedName = ""; for (auto c : name) { if (!std::isspace(c)) { trimmedName += c; } } std::string trimmedValue = ""; int sliceStart = 0; int sliceEnd = value.length(); if (value.length() >= 2) { for (unsigned short t = 0; t < value.length(); t++) { if (!std::isspace(value[t])) { sliceStart = t; break; } } for (int t = value.length()-1; t >= 0; t--) { if (!std::isspace(value[t])) { sliceEnd = t; break; } } // Add and subtract 1 from each side to remove quotes for (unsigned short t = sliceStart+1; t < sliceEnd; t++) { trimmedValue += value[t]; } } attrs[trimmedName] = trimmedValue; } } return attrs; } // !TODO: Make a html string parser as well std::unique_ptr
parserdocument(std::string file) { std::ifstream inputFile(file); if (!inputFile.is_open()) { std::cerr << "Unable to open: " << file << std::endl; return nullptr; } std::unique_ptr
root = std::make_unique
(); root->setTagName("root"); std::string line; Node* currentNode = root.get(); // !ISSUE: test // bool inTag = false; bool escaped = false; bool inQuote = false; char quoteType = '"'; bool inClosing = false; std::vector
tokens; std::string word = ""; // !TODO: self closing tags
while (std::getline(inputFile, line)) { int ll = line.length(); for (unsigned short i = 0; i < ll; i++) { char current = line[i]; if (current == '/' && inTag) { currentNode = currentNode->parent; inClosing = true; word = ""; inTag = false; tokens.clear(); } // Hijack the closing skipping to skipp over comments if (current == '!' && inTag) { inClosing = true; } // Create the element outside of any tag parts if (!inTag && tokens.size() > 0 && !inClosing && current != '<') { // Build the element currentNode = currentNode->createElement(tokens[0]); auto attrs = parseAttributes(tokens); for (auto const& pair : attrs) { // All attributes are stored as strings so we can just throw them in currentNode->setAttribute
(pair.first, pair.second); } tokens.clear(); word = ""; } if (current == '>') { tokens.push_back(word); word = ""; inTag = false; inClosing = false; } if (inClosing) { continue; } if (inTag) { if ((current == '"' || current == '\'') && !escaped) { if (inQuote && current == quoteType) { inQuote = false; } else if (!inQuote) { inQuote = true; quoteType = current; } } if (current == ' ' && !inQuote) { tokens.push_back(word); word = ""; } else { word += current; } } else if (current != '<' && current != '>') { // Outside tag add to innerText currentNode->setInnerText(currentNode->getInnerText() + current); } if (current == '<') { inTag = true; } escaped = false; if (current == '\\') { //' escaped = true; } } } inputFile.close(); return root; } int main() { auto document = parserdocument("./index.html"); document->print(); return 0; }