home readme diff tree note docs

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