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+}