Load HTML into a document from a string, a file or a stream, let the parser pick the encoding from the bytes, and see the tokenizer’s parse errors when you need them.
Document.Parse has three overloads: bytes (ReadOnlyMemory<byte>), a string, and an already decoded SourceText. There is no Stream or file-path overload, because the library never reads files or the network. You read the input and pass the bytes.
Prefer bytes whenever you have them. With bytes, the parser applies the WHATWG encoding rules: byte order mark, then the transport Content-Type (ParseOptions.ContentType), then the <meta> prescan. A string is used as it is.
using System.Text; using RecursiveAI.OneHtml; // From a string Document fromString = Document.Parse("<p class=lead>Hello, <b>world</b>"); Console.WriteLine(fromString.Root.QuerySelector("p.lead").TextContent); Console.WriteLine(fromString.SerializeNode(fromString.DocumentElement)); // From a file: read the bytes and let the parser sniff the encoding Document fromFile = Document.Parse(File.ReadAllBytes("events.html")); Console.WriteLine($"{fromFile.Source.Encoding.Name}: {fromFile.Root.QuerySelector("h1").TextContent}"); // From a stream: copy it into memory first; Parse takes bytes, not a Stream using Stream stream = File.OpenRead("events.html"); using var buffer = new MemoryStream(); await stream.CopyToAsync(buffer); Document fromStream = Document.Parse(buffer.GetBuffer().AsMemory(0, (int)buffer.Length)); Console.WriteLine($"{fromStream.NodeCount} nodes"); // Tokenizer errors are recorded only when asked for var options = new ParseOptions { RecordErrors = true }; Document broken = Document.Parse("<p title=\"a\"b>Fish &chips</p>", options); foreach (var error in broken.ParseErrors) { Console.WriteLine($"{error.Error} at offset {error.Offset}"); } Console.WriteLine(broken.SerializeNode(broken.Root.QuerySelector("p")));
Hello, world <html><head></head><body><p class="lead">Hello, <b>world</b></p></body></html> UTF-8: Upcoming events 101 nodes MissingWhitespaceBetweenAttributes at offset 12 <p title="a" b="">Fish &chips</p>
Some things to know:
- A string input still gets the full document:
<html>,<head>and<body>are created as a browser would create them. To parse a snippet without them, see HTML fragments. buffer.GetBuffer().AsMemory(0, (int)buffer.Length)hands over the stream’s bytes without a second copy.buffer.ToArray()works too.- Parse errors never throw. They are recorded only when
ParseOptions.RecordErrorsis set, and only the tokenizer’s errors are recorded (not tree construction errors). Without it,ParseErrorsis empty. - Hostile-input limits (input size, nesting depth, attribute count) are checked before the work they guard. Going past one throws
HtmlLimitExceededException. Set your own withParseOptions.Limits. SerializeNodeandSerializeChildrenwrite the HTML serialization of the tree. To write the page back with its original bytes, useToLosslessBytes()(see Edit HTML without reformatting it).
Encoding detection in detail: Detect the encoding of an HTML page.
A longer guide with encoding detection and broken markup: Parse HTML in C#.
Fetching, encodings, robots.txt and JavaScript-built pages: Web scraping in C#.