NkkinParser 2.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package NkkinParser --version 2.1.0
                    
NuGet\Install-Package NkkinParser -Version 2.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NkkinParser" Version="2.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NkkinParser" Version="2.1.0" />
                    
Directory.Packages.props
<PackageReference Include="NkkinParser" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NkkinParser --version 2.1.0
                    
#r "nuget: NkkinParser, 2.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NkkinParser@2.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NkkinParser&version=2.1.0
                    
Install as a Cake Addin
#tool nuget:?package=NkkinParser&version=2.1.0
                    
Install as a Cake Tool

NkkinParser

A fast, low-allocation HTML5 parser and CSS selector engine for .NET 10. No dependencies.

NkkinParser builds the same DOM a browser would from messy real-world HTML, then lets you query it with CSS selectors. On the project's benchmarks it parses about 2.7× faster than AngleSharp and 3.3× faster than HtmlAgilityPack with roughly half of AngleSharp's allocations, and it matches AngleSharp's results on 28 real pages (identical body text, identical results for 840 selector checks).

Install

dotnet add package NkkinParser

Targets net10.0.

Quick start

using NkkinParser;

var document = HtmlParser.Parse(html);

foreach (var link in document.QuerySelectorAll("a[href]"))
    Console.WriteLine($"{link.TextContent} -> {link.ResolveUrl()}");

Console.WriteLine(document.Title);
Console.WriteLine(document.QuerySelector("article")?.ToPlainText());

// Selectors Level 4 and a DOM you can edit.
foreach (var card in document.QuerySelectorAll("div.card:has(> img):not(:is(.ad, .promo))"))
{
    card.AddClass("seen");
    card.QuerySelector("script")?.Remove();
}

More entry points:

using var fromBytes = new HtmlParser(bytes);                      // encoding detected from BOM / <meta charset>
var arabic     = HtmlParser.Parse(bytes, EncodingDetector.GetEncoding("windows-1256")); // HTTP charset wins over <meta>
var fromStream = HtmlParser.Parse(File.OpenRead("page.html"));
var fromUrl    = await HtmlLoader.LoadAsync("https://example.com/");   // HTTP/HTTPS, compressed, size-capped, header charset, BaseUri set

var index = new NkkinParser.Indexing.DocumentIndex(document);     // build once, reuse for many queries
var cards = document.QuerySelectorAll("div.card > a[href], #search", index);

string html2 = element.OuterHtml;                                 // also InnerHtml, TextContent

Streaming without a DOM:

var reader = new NkkinPullParser(html);
while (reader.Read())
    if (reader.CurrentType == NodeType.Element && reader.TryGetAttribute("href", out var href)) Console.WriteLine(href);

await foreach (var node in HtmlParser.ParseAsync(stream, cancellationToken)) { /* lazily yielded nodes */ }

Features

  • Browser-like tree construction: implied html/head/body, the WHATWG adoption agency algorithm, tables with foster-parenting and implied tbody/tr, <template>, auto-closing p/li/dt/dd/headings/option/ruby, scope-aware end tags, and correct raw-text handling for script, style, title, textarea, iframe, xmp.
  • Inline SVG and MathML: Element.Namespace, camelCase names kept (viewBox, foreignObject), /> honoured.
  • All 2,231 HTML named entities, legacy names without a semicolon, and the attribute ambiguity rule.
  • Encoding detection from the BOM, the HTTP charset and <meta charset>, in browser order; latin1/iso-8859-1/ascii decode as windows-1252, as in browsers.
  • Selectors: type, *, #id, .class (with CSS escapes such as .md\:flex), all attribute operators (with i), combinators ( , >, +, ~), lists, :is(), :where(), :has(), :not(), :scope, :first-child, :last-child, :only-child, :nth-child(An+B [of S]), :nth-last-child(), the -of-type family, :empty, :root, :checked, :disabled, :enabled, :required, :optional, :read-only, :read-write, :placeholder-shown, :link/:any-link, and the jQuery-style :contains("text"). Unsupported pseudo-classes match nothing.
  • DOM API: Id, ClassList, GetAttribute/SetAttribute/RemoveAttribute, AddClass/RemoveClass, Children, Closest, Matches, Descendants(), AppendChild/InsertBefore/Remove/ReplaceWith/CloneNode, Document.Title/Head/Body/CreateElement, ResolveUrl() (honours <base href>), and ToPlainText().
  • Robust on untrusted input: iterative tree walks (100,000-level nesting is fine), bounded selector cache, response-size cap in HtmlLoader, seeded fuzz tests.
  • Opt-in node pooling (new HtmlParser(html, enableNodePooling: true)) for parse loops; do not read the tree after disposing the parser in that mode.

How the DOM behaves

  • <p>x</p> becomes html > head + body > p, so QuerySelector("body") always works.
  • Text is whitespace-collapsed and trimmed at block boundaries, keeping one space between inline elements (Hello <b>world</b> reads Hello world); pre, listing, textarea, script and style are verbatim.
  • <template> content is inert, as in a browser: document-level queries, DocumentIndex and an ancestor's TextContent skip it. Query the template element itself (Element.IsTemplate) to reach it.
  • Comment and doctype nodes hold only their data or name, not the delimiters.
  • A tag still open at the end of the input is dropped.

Performance

Typical of a few short BenchmarkDotNet runs (i3-8100, .NET 10, AngleSharp 1.8.2, HtmlAgilityPack 1.13.0), on synthetic documents. Compare the ratios; absolute times vary by machine (about ±10% between runs).

Benchmark AngleSharp HtmlAgilityPack NkkinParser
Parse (three documents, ~10 KB to 1 MB) 110 ms / 40 MB 134 ms / 79 MB 41 ms / 21 MB
Parse + serialize 240 ms / 60 MB 200 ms / 102 MB 50 ms / 25–33 MB
CSS selector query, 6,002 matches 0.70 ms / 128 KB 312 ms / 4,977 KB (XPath) 0.36 ms / 51 KB

Allocation figures were confirmed with GC.GetAllocatedBytesForCurrentThread because BenchmarkDotNet's allocation column is unreliable on this suite. Methodology, the full tables and reproduction steps are in the repository README.

Verification

323 unit tests, seeded fuzzing, about 87% of the html5lib tree-construction cases (86.8% of 957 at this release), and a real-page comparison against AngleSharp. See How it is verified.

Limitations

Not a browser engine (no scripting, CSS cascade or layout); <frameset> and <plaintext> are not modelled; a few deep table/formatting corner cases differ from the standard; no user-action pseudo-classes (:hover, :focus) or pseudo-elements; no fragment parsing (InnerHtml is read-only).

MIT licensed.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.1.1 54 11/4/2026
2.1.0 54 11/4/2026
2.0.0 153 9/21/2026
1.0.0 247 8/3/2026

2.1.0: :is(), :where(), :has(), :nth-child(An+B of S), :scope, form-state pseudo-classes and :contains(); a public DOM editing and navigation API; URL resolution; ToPlainText(); HTTP charset support in HtmlLoader; much cheaper small parses; several fixes. See https://github.com/Nkkinsoft/NkkinParser/blob/main/CHANGELOG.md.