home
readme
diff
tree
note
docs
0commit e36ac5417e10ee9b9f94f340e1ccf28afc5705ea
1Author: Mason Wright <mason@lakefox.net>
2Date: Mon Jun 16 17:56:46 2025 -0600
3
4 About to rework the parser
5
6diff --git a/element.cc b/element.cc
7index 5e0ccf6..059e344 100755
8--- a/element.cc
9+++ b/element.cc
10@@ -1,6 +1,7 @@
11 #include <string>
12 #include <vector>
13 #include <fstream>
14+#include <sstream>
15 #include <unordered_map>
16 #include <iostream>
17 #include <memory>
18@@ -75,6 +76,8 @@ class ClassList {
19 }
20 };
21
22+// !TODO: Cascade the styles during the parsing of the document and add the styles during the css parsing
23+// + all other styles should cascade when added
24 class Node {
25 private:
26 std::string TagName;
27@@ -313,14 +316,7 @@ std::unordered_map<std::string, std::string> parseAttributes(std::vector<std::st
28 }
29
30 // !TODO: Make a html string parser as well
31-std::unique_ptr<Node> parserdocument(std::string file) {
32- std::ifstream inputFile(file);
33-
34- if (!inputFile.is_open()) {
35- std::cerr << "Unable to open: " << file << std::endl;
36- return nullptr;
37- }
38-
39+std::unique_ptr<Node> parseStream(std::istream& inputStream) {
40 std::unique_ptr<Node> root = std::make_unique<Node>();
41 root->setTagName("root");
42
43@@ -332,99 +328,92 @@ std::unique_ptr<Node> parserdocument(std::string file) {
44 bool escaped = false;
45 bool inQuote = false;
46 char quoteType = '"';
47- bool inClosing = false;
48+ bool isClosing = false;
49
50 std::vector<std::string> tokens;
51 std::string word = "";
52
53+ char current;
54+
55 // !TODO: self closing tags <br> <br/> <input />
56- while (std::getline(inputFile, line)) {
57- int ll = line.length();
58- for (unsigned short i = 0; i < ll; i++) {
59- char current = line[i];
60-
61- if (current == '/' && inTag) {
62- currentNode = currentNode->parent;
63- inClosing = true;
64+ // + also tags with no content
65+ while (inputStream.get(current)) {
66+ std::cout << "--------" << std::endl;
67+ std::cout << current << std::endl;
68+ if (isClosing && current != '>') {
69+ std::cout << "Continuing no >" << std::endl;
70+ continue;
71+ } else if (isClosing && current == '>') {
72+ std::cout << "Continuing at >" << std::endl;
73+ isClosing = false;
74+ continue;
75+ }
76+
77+ if (inTag) {
78+ std::cout << "In Tag" << std::endl;
79+ if (current == '/') {
80+ std::cout << "End Tag" << std::endl;
81+ isClosing = true;
82+ } else if (current == '>' && !escaped) {
83+ tokens.push_back(word);
84+ std::cout << "Creating Element: " << tokens[0] << std::endl;
85+ // Add the last word
86+
87 word = "";
88 inTag = false;
89- tokens.clear();
90- }
91-
92- // Hijack the closing skipping to skipp over comments
93- if (current == '!' && inTag) {
94- inClosing = true;
95- }
96-
97- // Create the element outside of any tag parts
98- if (!inTag && tokens.size() > 0 && !inClosing && current != '<') {
99 // Build the element
100 currentNode = currentNode->createElement(tokens[0]);
101 auto attrs = parseAttributes(tokens);
102 for (auto const& pair : attrs) {
103 // All attributes are stored as strings so we can just throw them in
104- currentNode->setAttribute<std::string>(pair.first, pair.second);
105+ currentNode->setAttribute(pair.first, pair.second);
106 }
107
108- tokens.clear();
109- word = "";
110- }
111-
112- if (current == '>') {
113- tokens.push_back(word);
114- word = "";
115- inTag = false;
116- inClosing = false;
117- }
118-
119- if (inClosing) {
120- continue;
121- }
122-
123- if (inTag) {
124
125- if ((current == '"' || current == '\'') && !escaped) {
126- if (inQuote && current == quoteType) {
127- inQuote = false;
128- } else if (!inQuote) {
129- inQuote = true;
130- quoteType = current;
131- }
132- }
133- if (current == ' ' && !inQuote) {
134- tokens.push_back(word);
135- word = "";
136- } else {
137- word += current;
138+ } else if ((current == '"' || current == '\'') && !escaped) {
139+ if (inQuote && current == quoteType) {
140+ inQuote = false;
141+ } else if (!inQuote) {
142+ inQuote = true;
143+ quoteType = current;
144 }
145- } else if (current != '<' && current != '>') {
146- // Outside tag add to innerText
147- currentNode->setInnerText(currentNode->getInnerText() + current);
148 }
149-
150- if (current == '<') {
151- inTag = true;
152+ if (current == ' ' && !inQuote) {
153+ tokens.push_back(word);
154+ word = "";
155+ } else {
156+ word += current;
157 }
158
159- escaped = false;
160+ } else if (current != '<' && current != '>') {
161+ std::cout << "Adding innerText to " << currentNode->getTagName() <<":"<< current << std::endl;
162+ // Outside tag add to innerText
163+ currentNode->setInnerText(currentNode->getInnerText() + current);
164+ }
165+
166+ if (current == '<' && !escaped) {
167+ tokens.clear();
168+ word = "";
169+ inTag = true;
170+ }
171
172- if (current == '\\') { //'
173- escaped = true;
174- }
175+ escaped = false;
176
177-
178+ if (current == '\\') { //'
179+ escaped = true;
180 }
181 }
182
183- inputFile.close();
184-
185 return root;
186 }
187
188
189 int main() {
190+ // std::string html1 = "<h1></h1>";
191+ // std::stringstream ss1(html1);
192
193- auto document = parserdocument("./index.html");
194+ std::ifstream inputFile("./index.html");
195+ auto document = parseStream(inputFile);
196
197 document->print();
198 return 0;
199diff --git a/index.html b/index.html
200index 2f0e2ac..9e83031 100644
201--- a/index.html
202+++ b/index.html
203@@ -2,8 +2,9 @@
204 lang="en thie i\"s\" a test"
205 contentEditable
206 >
207- hi
208+<h1></h1>
209 <p id="test">
210 this is a test
211+ <input type='text'/>
212 </p>
213 </html>
214diff --git a/main b/main
215index ac8b53e..6c1b027 100755
216Binary files a/main and b/main differ
217diff --git a/test.cc b/test.cc
218new file mode 100644
219index 0000000..a53d635
220--- /dev/null
221+++ b/test.cc
222@@ -0,0 +1,459 @@
223+#include <iostream>
224+#include <string>
225+#include <vector>
226+#include <memory>
227+#include <map>
228+#include <sstream> // For stringstream input, useful for testing
229+#include <fstream> // For file input
230+
231+// Forward declarations to define Node and parseAttributes before parseHtmlStream
232+class Node;
233+std::map<std::string, std::string> parseAttributes(const std::vector<std::string>& tokens);
234+
235+/**
236+ * @brief Represents a node in the HTML Document Object Model (DOM) tree.
237+ * Can be an element node (e.g., <div>), a text node (innerText), or the root.
238+ */
239+class Node {
240+public:
241+ std::string tagName; // The tag name of the element (e.g., "div", "h1")
242+ std::string innerText; // Text content directly inside this node
243+ std::map<std::string, std::string> attributes; // Map of attribute key-value pairs
244+ std::vector<std::unique_ptr<Node>> children; // Vector of unique pointers to child nodes
245+ Node* parent; // Raw pointer to the parent node (ownership managed by unique_ptr in children)
246+
247+ /**
248+ * @brief Constructor for Node. Initializes parent to nullptr.
249+ */
250+ Node() : parent(nullptr) {}
251+
252+ // --- Setters ---
253+ void setTagName(const std::string& name) { tagName = name; }
254+ void setInnerText(const std::string& text) { innerText = text; }
255+ void setAttribute(const std::string& key, const std::string& value) { attributes[key] = value; }
256+
257+ // --- Getters ---
258+ const std::string& getTagName() const { return tagName; }
259+ const std::string& getInnerText() const { return innerText; }
260+ const std::map<std::string, std::string>& getAttributes() const { return attributes; }
261+
262+ /**
263+ * @brief Creates a new child node for the current node.
264+ * Sets the new node's tag name and its parent pointer to `this`.
265+ * Transfers ownership of the new node to the `children` vector.
266+ * @param name The tag name of the new element (e.g., "p", "a").
267+ * @return A raw pointer to the newly created child node.
268+ */
269+ Node* createElement(const std::string& name) {
270+ std::unique_ptr<Node> newNode = std::make_unique<Node>();
271+ newNode->setTagName(name);
272+ newNode->parent = this; // Set parent pointer for the new node
273+ Node* rawPtr = newNode.get(); // Get raw pointer before moving ownership
274+ children.push_back(std::move(newNode)); // Transfer ownership to the children vector
275+ return rawPtr;
276+ }
277+
278+ /**
279+ * @brief Prints the HTML tree structure recursively for debugging purposes.
280+ * Includes indentation for better readability.
281+ * @param indent The current indentation level.
282+ */
283+ void print(int indent = 0) const {
284+ // Print indentation for the current node
285+ for (int i = 0; i < indent; ++i) std::cout << " ";
286+
287+ // Print the opening tag and its attributes
288+ std::cout << "<" << tagName;
289+ for (const auto& attr : attributes) {
290+ std::cout << " " << attr.first << "=\"" << attr.second << "\"";
291+ }
292+ std::cout << ">";
293+
294+ // Print inner text if it exists
295+ if (!innerText.empty()) {
296+ std::cout << innerText;
297+ }
298+
299+ // Determine if a closing tag is required.
300+ // If the node has children or inner text, it definitely needs a closing tag.
301+ // Even for empty tags (like <h1></h1> with no content), a closing tag is required.
302+ bool requiresClosingTag = true; // Assuming all elements created require a closing tag
303+ // unless explicitly handled as self-closing in parser.
304+
305+ // If the node has content (text or children), print a newline for better formatting.
306+ // Then recursively print children, followed by the closing tag.
307+ if (!innerText.empty() || !children.empty()) {
308+ std::cout << std::endl; // Newline after opening tag if content/children follow
309+
310+ // Recursively print all child nodes
311+ for (const auto& child : children) {
312+ child->print(indent + 1);
313+ }
314+
315+ // Print indentation for the closing tag
316+ for (int i = 0; i < indent; ++i) std::cout << " ";
317+ std::cout << "</" << tagName << ">" << std::endl;
318+ } else if (requiresClosingTag) {
319+ // For truly empty tags (e.g., <h1></h1>), print newline and then its closing tag
320+ std::cout << std::endl;
321+ for (int i = 0; i < indent; ++i) std::cout << " ";
322+ std::cout << "</" << tagName << ">" << std::endl;
323+ } else {
324+ // This case handles conceptual self-closing for elements that don't need a closing tag
325+ // (e.g. <br/>). In the parser, `currentNode` immediately moves up for these.
326+ // So, for the `print` function, if it reaches this branch, it means the node was effectively "closed".
327+ std::cout << std::endl; // Just print a newline after the self-closing tag for formatting.
328+ }
329+ }
330+};
331+
332+/**
333+ * @brief Helper function to parse attributes from a vector of tokens.
334+ * It expects tokens to be either `key=value` (with or without quotes) or just `key` (for boolean attributes).
335+ * @param tokens A vector of strings, where the first element is the tag name, and subsequent elements are attributes.
336+ * @return A map of attribute keys to their values.
337+ */
338+std::map<std::string, std::string> parseAttributes(const std::vector<std::string>& tokens) {
339+ std::map<std::string, std::string> attrs;
340+ if (tokens.empty()) return attrs;
341+
342+ // Skip the first token which is the tag name itself
343+ for (size_t i = 1; i < tokens.size(); ++i) {
344+ const std::string& token = tokens[i];
345+ size_t eqPos = token.find('='); // Find the position of '='
346+
347+ if (eqPos != std::string::npos) {
348+ // Attribute is in `key=value` format
349+ std::string key = token.substr(0, eqPos);
350+ std::string value = token.substr(eqPos + 1);
351+
352+ // Remove quotes if the value is enclosed in them (e.g., value="my value")
353+ if (!value.empty() && (value.front() == '"' || value.front() == '\'')) {
354+ if (value.length() >= 2 && value.back() == value.front()) { // Ensure matching quotes
355+ value = value.substr(1, value.length() - 2);
356+ }
357+ }
358+ attrs[key] = value;
359+ } else {
360+ // Attribute is a boolean attribute or a key without an explicit value (e.g., `disabled`)
361+ attrs[token] = "";
362+ }
363+ }
364+ return attrs;
365+}
366+
367+/**
368+ * @brief Core HTML parsing logic that reads character by character from an input stream.
369+ * @param inputStream The input stream (e.g., `ifstream` for file, `stringstream` for string).
370+ * @return A unique_ptr to the root Node of the parsed HTML tree.
371+ */
372+std::unique_ptr<Node> parseHtmlStream(std::istream& inputStream) {
373+ std::unique_ptr<Node> root = std::make_unique<Node>();
374+ root->setTagName("root"); // Create a conceptual root node for the document
375+
376+ Node* currentNode = root.get(); // Pointer to the currently active node in the tree
377+
378+ std::string currentTokenPart = ""; // Buffer for collecting tag names, attribute keys, or partial attribute values
379+ std::string textBuffer = ""; // Buffer for collecting inner text content
380+
381+ bool inTag = false; // True when parser is inside '<' and '>' of a tag
382+ bool inClosingTag = false; // True when parsing a closing tag (e.g., after `</`)
383+ bool inAttributeValue = false; // True when parsing a quoted attribute value (e.g., `value="...")
384+ char attributeQuoteType = ' '; // Stores the type of quote (`'` or `"`) used for the attribute value
385+
386+ std::vector<std::string> tagTokens; // Stores the tag name and all its attributes as individual tokens
387+
388+ char current; // Current character being processed
389+
390+ // Loop through the input stream character by character
391+ while (inputStream.get(current)) {
392+ if (inTag) {
393+ // --- Parser is currently inside an HTML tag (`<...>` or `</...>`) ---
394+ if (inAttributeValue) {
395+ // Currently parsing content within an attribute's quoted value
396+ if (current == attributeQuoteType) {
397+ // End of the attribute value (closing quote found)
398+ inAttributeValue = false;
399+ tagTokens.push_back(currentTokenPart); // Add the complete attribute value to tokens
400+ currentTokenPart = ""; // Reset buffer for next part
401+ } else {
402+ currentTokenPart += current; // Continue accumulating the attribute value
403+ }
404+ } else if (current == ' ' || current == '\t' || current == '\n' || current == '\r') {
405+ // Whitespace: separates tag name, attributes, or self-closing markers
406+ if (!currentTokenPart.empty()) {
407+ tagTokens.push_back(currentTokenPart); // Add the accumulated part (tag name or attribute key/value)
408+ currentTokenPart = ""; // Reset buffer
409+ }
410+ } else if (current == '=' && !inClosingTag) {
411+ // Encountered an equals sign (for `key=value` attributes)
412+ if (!currentTokenPart.empty()) {
413+ tagTokens.push_back(currentTokenPart); // Push the attribute key
414+ currentTokenPart = "";
415+ }
416+ currentTokenPart += current; // Add the '=' to the current part (it will be combined with value later)
417+ } else if (current == '"' || current == '\'') {
418+ // Start of an attribute value (quoted)
419+ // If there's an accumulated part, it should be the key or `key=`
420+ if (!currentTokenPart.empty()) {
421+ tagTokens.push_back(currentTokenPart);
422+ }
423+ currentTokenPart = ""; // Clear for the actual value
424+ inAttributeValue = true;
425+ attributeQuoteType = current; // Store the quote type
426+ } else if (current == '/') {
427+ // Encountered a slash (`/`). Could be for:
428+ // 1. Self-closing tag (e.g., `<br/>`, `<input />`)
429+ // 2. Start of a closing tag (e.g., `</div`) - this is already handled by `inClosingTag` check before `inTag` starts
430+ if (!currentTokenPart.empty()) {
431+ tagTokens.push_back(currentTokenPart);
432+ }
433+ currentTokenPart = ""; // Reset for '/' itself
434+ currentTokenPart += current; // Add '/' as a token part
435+ } else if (current == '>') {
436+ // --- End of a tag (`>` found) ---
437+ inTag = false; // Exiting the tag processing state
438+
439+ // If there's any remaining accumulated part, add it to tokens
440+ if (!currentTokenPart.empty()) {
441+ tagTokens.push_back(currentTokenPart);
442+ currentTokenPart = "";
443+ }
444+
445+ if (inClosingTag) {
446+ // This was a closing tag (e.g., `</h1>`)
447+ if (!tagTokens.empty()) {
448+ std::string closingTagName = tagTokens[0];
449+ // Trim trailing '/' if it was parsed as part of the tag name (e.g. `br/` due to tokenization)
450+ if (!closingTagName.empty() && closingTagName.back() == '/') {
451+ closingTagName.pop_back();
452+ }
453+
454+ // Check if the closing tag matches the current node's tag name
455+ if (currentNode->getTagName() == closingTagName) {
456+ if (currentNode->parent) {
457+ currentNode = currentNode->parent; // Move up to the parent node
458+ } else {
459+ // Error: Tried to close the root node or unmatched closing tag without a parent
460+ std::cerr << "Warning: Attempted to close root tag or unmatched closing tag: </" << closingTagName << ">" << std::endl;
461+ }
462+ } else {
463+ // Mismatched closing tag (e.g., `<div><span></div>`)
464+ std::cerr << "Error: Mismatched closing tag. Expected </" << currentNode->getTagName()
465+ << ">, got </" << closingTagName << ">" << std::endl;
466+ // For robustness, you might choose to still move up or try to find a matching ancestor.
467+ // For this simple parser, we'll still attempt to move up, assuming it's a parse error.
468+ if (currentNode->parent) {
469+ currentNode = currentNode->parent;
470+ }
471+ }
472+ } else {
473+ std::cerr << "Warning: Empty closing tag detected (e.g., </>)!" << std::endl;
474+ }
475+ inClosingTag = false; // Reset the closing tag state
476+ } else {
477+ // This was an opening tag (e.g., `<h1>`, `<div id="x">`)
478+ // Or a self-closing tag (e.g., `<br/>`, `<img />`)
479+ if (!tagTokens.empty()) {
480+ std::string tagName = tagTokens[0]; // The first token is the tag name
481+ bool isSelfClosing = false;
482+
483+ // Check if the tag is self-closing
484+ // 1. If the tag name itself ends with '/' (e.g., `<br/>` where `br/` is the token)
485+ if (!tagName.empty() && tagName.back() == '/') {
486+ isSelfClosing = true;
487+ tagName.pop_back(); // Remove the '/' from the tag name
488+ }
489+ // 2. If the last token is just "/" (e.g., `<input />` where `/` is a separate token)
490+ else if (!tagTokens.empty() && tagTokens.back() == "/") {
491+ isSelfClosing = true;
492+ tagTokens.pop_back(); // Remove the "/" token from the list
493+ }
494+
495+ // Create the new node as a child of the current node
496+ Node* newNode = currentNode->createElement(tagName);
497+ // Parse attributes from the collected tokens
498+ auto attrs = parseAttributes(tagTokens);
499+ for (auto const& pair : attrs) {
500+ newNode->setAttribute(pair.first, pair.second);
501+ }
502+ currentNode = newNode; // Move `currentNode` down to the newly created element
503+
504+ if (isSelfClosing) {
505+ // For self-closing tags, immediately move back up to the parent
506+ // as they do not contain children or inner text.
507+ if (currentNode->parent) {
508+ currentNode = currentNode->parent;
509+ }
510+ }
511+ } else {
512+ std::cerr << "Warning: Empty or malformed opening tag detected: < >" << std::endl;
513+ }
514+ }
515+ tagTokens.clear(); // Clear tokens for the next tag
516+ textBuffer = ""; // Clear any accumulated text before a new tag or content starts
517+ } else {
518+ // Accumulate characters for the tag name or an attribute key/value part
519+ currentTokenPart += current;
520+ }
521+ } else {
522+ // --- Parser is currently outside a tag, processing text content or looking for new tags ---
523+ if (current == '<') {
524+ // A new tag is starting.
525+ // If there's accumulated text, assign it as innerText to the current node
526+ if (!textBuffer.empty()) {
527+ // Trim leading/trailing whitespace/newlines from the text content
528+ size_t first = textBuffer.find_first_not_of(" \t\n\r");
529+ size_t last = textBuffer.find_last_not_of(" \t\n\r");
530+ if (std::string::npos != first) {
531+ currentNode->setInnerText(textBuffer.substr(first, (last - first + 1)));
532+ }
533+ textBuffer = ""; // Reset text buffer for next content
534+ }
535+
536+ // Peek at the next character to determine the type of tag
537+ char nextChar;
538+ if (inputStream.get(nextChar)) { // Attempt to read the next character without consuming it fully yet
539+ if (nextChar == '/') {
540+ // It's a closing tag (e.g., `</div`)
541+ inClosingTag = true;
542+ inTag = true; // Enter the tag parsing state
543+ } else if (nextChar == '!') {
544+ // It's a comment (`<!-- ... -->`) or DOCTYPE (`<!DOCTYPE ...>`)
545+ // Simple skipping logic: read until the next `>`
546+ while (inputStream.get(current) && current != '>');
547+ continue; // Continue to the next character in the main loop
548+ } else {
549+ // It's a regular opening tag (e.g., `<div>`)
550+ inTag = true; // Enter the tag parsing state
551+ currentTokenPart += nextChar; // Add the actual tag name character
552+ }
553+ } else {
554+ // Unexpected end of file after '<'
555+ std::cerr << "Warning: Unexpected EOF after '<'" << std::endl;
556+ break; // Exit parsing loop
557+ }
558+ } else {
559+ // Accumulate characters as inner text content for the current node
560+ textBuffer += current;
561+ }
562+ }
563+ }
564+
565+ // After the loop, if there's any remaining text in the buffer, assign it
566+ if (!textBuffer.empty()) {
567+ size_t first = textBuffer.find_first_not_of(" \t\n\r");
568+ size_t last = textBuffer.find_last_not_of(" \t\n\r");
569+ if (std::string::npos != first) {
570+ currentNode->setInnerText(textBuffer.substr(first, (last - first + 1)));
571+ }
572+ }
573+
574+ return root; // Return the root of the parsed HTML tree
575+}
576+
577+/**
578+ * @brief Parses an HTML file and constructs a DOM tree.
579+ * @param file The path to the HTML file.
580+ * @return A unique_ptr to the root Node of the parsed HTML tree, or nullptr if the file cannot be opened.
581+ */
582+std::unique_ptr<Node> parserdocument(std::string file) {
583+ std::ifstream inputFile(file); // Open the specified file
584+
585+ if (!inputFile.is_open()) {
586+ std::cerr << "Unable to open: " << file << std::endl;
587+ return nullptr; // Return nullptr if file opening fails
588+ }
589+
590+ return parseHtmlStream(inputFile); // Use the core parsing logic
591+}
592+
593+
594+// --- Main function for demonstration and testing ---
595+int main() {
596+ // Test Case 1: Empty tag <h1></h1>
597+ std::cout << "--- Test Case 1: Empty tag <h1></h1> ---" << std::endl;
598+ std::string html1 = "<h1></h1>";
599+ std::stringstream ss1(html1);
600+ std::unique_ptr<Node> root1 = parseHtmlStream(ss1);
601+ if (root1) {
602+ root1->print();
603+ }
604+ std::cout << "\n";
605+
606+ // Test Case 2: Tag with text content <div>Hello World</div>
607+ std::cout << "--- Test Case 2: Tag with text content <div>Hello World</div> ---" << std::endl;
608+ std::string html2 = "<div>Hello World</div>";
609+ std::stringstream ss2(html2);
610+ std::unique_ptr<Node> root2 = parseHtmlStream(ss2);
611+ if (root2) {
612+ root2->print();
613+ }
614+ std::cout << "\n";
615+
616+ // Test Case 3: Nested tags with mixed content
617+ std::cout << "--- Test Case 3: Nested tags with mixed content ---" << std::endl;
618+ std::string html3 = "<body><p>This is <b>bold</b> text.</p></body>";
619+ std::stringstream ss3(html3);
620+ std::unique_ptr<Node> root3 = parseHtmlStream(ss3);
621+ if (root3) {
622+ root3->print();
623+ }
624+ std::cout << "\n";
625+
626+ // Test Case 4: Tags with attributes and self-closing tags
627+ std::cout << "--- Test Case 4: Tags with attributes and self-closing tags ---" << std::endl;
628+ std::string html4 = "<div id=\"main\" class='container'><img src=\"image.jpg\" alt=\"My Image\" /><br></div>";
629+ std::stringstream ss4(html4);
630+ std::unique_ptr<Node> root4 = parseHtmlStream(ss4);
631+ if (root4) {
632+ root4->print();
633+ }
634+ std::cout << "\n";
635+
636+ // Test Case 5: Empty tag with whitespace between
637+ std::cout << "--- Test Case 5: Empty tag with whitespace between <h1> </h1> ---" << std::endl;
638+ std::string html5 = "<h1> </h1>";
639+ std::stringstream ss5(html5);
640+ std::unique_ptr<Node> root5 = parseHtmlStream(ss5);
641+ if (root5) {
642+ root5->print();
643+ }
644+ std::cout << "\n";
645+
646+ // Test Case 6: Malformed closing tag
647+ std::cout << "--- Test Case 6: Malformed closing tag (mismatch) ---" << std::endl;
648+ std::string html6 = "<div><span></div>"; // Mismatched closing tag
649+ std::stringstream ss6(html6);
650+ std::unique_ptr<Node> root6 = parseHtmlStream(ss6);
651+ if (root6) {
652+ root6->print();
653+ }
654+ std::cout << "\n";
655+
656+ // Test Case 7: Comment and DOCTYPE
657+ std::cout << "--- Test Case 7: Comment and DOCTYPE ---" << std::endl;
658+ std::string html7 = "<!DOCTYPE html>\n<!-- This is a comment --><body>Content</body>";
659+ std::stringstream ss7(html7);
660+ std::unique_ptr<Node> root7 = parseHtmlStream(ss7);
661+ if (root7) {
662+ root7->print();
663+ }
664+ std::cout << "\n";
665+
666+ // Example of using parserdocument function with a file (requires a test.html file)
667+ // std::cout << "--- Test Case 8: Parsing from file (test.html) ---" << std::endl;
668+ // // Create a dummy test.html file for this test
669+ // std::ofstream outfile("test.html");
670+ // outfile << "<html><body><p>Hello from file!</p></body></html>";
671+ // outfile.close();
672+ //
673+ // std::unique_ptr<Node> root8 = parserdocument("test.html");
674+ // if (root8) {
675+ // root8->print();
676+ // } else {
677+ // std::cout << "Failed to parse test.html" << std::endl;
678+ // }
679+
680+ return 0;
681+}