Net4x.HtmlCleaner
1.4.0.26249
dotnet add package Net4x.HtmlCleaner --version 1.4.0.26249
NuGet\Install-Package Net4x.HtmlCleaner -Version 1.4.0.26249
<PackageReference Include="Net4x.HtmlCleaner" Version="1.4.0.26249" />
<PackageVersion Include="Net4x.HtmlCleaner" Version="1.4.0.26249" />
<PackageReference Include="Net4x.HtmlCleaner" />
paket add Net4x.HtmlCleaner --version 1.4.0.26249
#r "nuget: Net4x.HtmlCleaner, 1.4.0.26249"
#:package Net4x.HtmlCleaner@1.4.0.26249
#addin nuget:?package=Net4x.HtmlCleaner&version=1.4.0.26249
#tool nuget:?package=Net4x.HtmlCleaner&version=1.4.0.26249
Net4x.HtmlCleaner
Turns messy, real-world HTML into well-formed XHTML.
It is built for the HTML that arrives from Microsoft Word and Outlook, from RSS feeds, and from
WYSIWYG editors: unclosed tags, <o:p> and <w:…> namespace noise, inline styles, event handlers
and CDATA leftovers. A dedicated pass strips a "Save as Web Page" export back to its content. The output is always parseable as XML, so it can be fed straight into
XmlDocument, XDocument, an XSLT transform or a template engine.
Targets .NET Framework 4.5, .NET Framework 4.6.1 and .NET Standard 2.0.
Install
dotnet add package Net4x.HtmlCleaner
Usage
Everything goes through CleanerHelper.Instance, which exposes three overloads of GenerateXHtml.
Each takes two independent switches: filterOutput for the tag/attribute whitelist, and
cleanWordMarkup for the Word clean up.
From a string
using HtmlCleaner;
string xhtml = CleanerHelper.Instance.GenerateXHtml("<p>unclosed paragraph");
// <html><p>unclosed paragraph</p></html>
From a file
CleanerHelper.Instance.GenerateXHtml(@"C:\in\word-export.html", @"C:\out\clean.xhtml");
From any reader to any writer
using (var input = new StreamReader(sourceStream))
using (var output = new StreamWriter(destinationStream))
{
CleanerHelper.Instance.GenerateXHtml(input, output);
}
The reader and the writer stay yours: the library flushes the writer but never closes either of them, so you can keep writing to the same destination afterwards.
What it always does
| Input | Output |
|---|---|
<p>unclosed paragraph |
<html><p>unclosed paragraph</p></html> |
<o:p>word noise</o:p><p>keep me</p> |
<html><p>keep me</p></html> |
<p>Tom & Jerry</p> |
<html><p>Tom & Jerry</p></html> |
<p>He said "hello"</p> |
<html><p>He said "hello"</p></html> |
<p>a + U+00A0 + b</p> (a non-breaking space) |
<html><p>a b</p></html> |
- Every element the source left open is closed, so the result is well-formed XML.
- Elements carrying a namespace prefix —
<o:p>,<w:WordDocument>,<v:shape>— are dropped together with their content, however many of them follow one another. &,<,>,'and"are escaped, and non-breaking spaces become .- Tag casing is preserved:
<P>stays<P>.
Because is not one of the five entities XML predefines, declare it before parsing the
result standalone:
const string prologue = "<!DOCTYPE html [<!ENTITY nbsp \" \">]>";
var document = new XmlDocument { XmlResolver = null };
document.LoadXml(prologue + xhtml);
Filtering
Pass filterOutput: true for a whitelist pass on top — useful when the HTML is going to be
embedded in a page you control.
string safe = CleanerHelper.Instance.GenerateXHtml(
"<p class='intro' onclick='steal()' style='color:red'>text</p>",
filterOutput: true);
// <dd><p class="intro">text</p></dd>
With filtering on:
- Tags outside the allowed list are renamed to the replacement tag (
ddby default) — the content survives, the element does not. Allowed by default:p b i u em big small div img span blockquote code pre br hr ul ol li del ins strong a font dd dt. Note thathtmlandbodyare not on that list, which is why the sample above is wrapped in<dd>. - Attributes outside the allowed list are dropped, which is what removes
onclick,styleand themso-*noise. Allowed by default:class href target border src align width height color size. The names that survive are written in lower case. - Comments are dropped.
- Text is trimmed, newlines (
\r\n,\rand\nalike) become spaces, and runs of consecutive spaces or collapse to one.
Tuning the whitelists
The lists live on HtmlWriter, so drive it directly when the defaults do not fit:
using HtmlCleaner.Text;
var builder = new StringBuilder();
using (var reader = new HtmlReader(dirtyHtml))
using (var writer = new HtmlWriter(builder))
{
writer.FilterOutput = true;
writer.AllowedTags = new[] { "p", "b", "i", "a" };
writer.AllowedAttributes = new[] { "href" };
writer.ReplacementTag = "span";
writer.ReduceConsecutiveSpace = false;
reader.Read();
while (!reader.EOF) writer.WriteNode(reader, true);
}
// writer must be closed - as it is here by the using - for the document to be completed
string xhtml = builder.ToString();
| Member | Default | Effect |
|---|---|---|
FilterOutput |
false |
Master switch for everything in this section. |
AllowedTags |
see above | Tags allowed through unchanged. |
ReplacementTag |
"dd" |
What a disallowed tag is renamed to. |
AllowedAttributes |
see above | Attributes allowed through. |
RemoveNewlines |
true |
Newlines in text become spaces. |
ReduceConsecutiveSpace |
true |
Runs of spaces or collapse to one. |
Cleaning a Word export
cleanWordMarkup: true strips what Microsoft Word adds when a document is saved as HTML. On a real
five-page contract exported from Word 15, that is 514 KB down to 14 KB with every paragraph,
table and character intact.
CleanerHelper.Instance.GenerateXHtml(
@"C:\in\DraftDonatie_Final.htm",
@"C:\out\clean.html",
cleanWordMarkup: true);
What it removes:
| Junk | What happens to it |
|---|---|
<meta>, <link> pointing at the _files folder, <style>, <script> |
removed with their content |
| `` settings blocks | removed — all comments are |
<o:p>, <w:…>, <v:…> elements and the xmlns:o/xmlns:w/xmlns:v declarations |
removed |
class=MsoNormal, style='mso-…', lang=EN-GB — every attribute |
removed |
<span>, <div>, <font> wrappers that only carried those attributes |
removed, children kept |
| the blank line each removed element left behind | removed |
What survives: <html><head><body>, the text, and the semantic markup — p, b, i, br,
table, tr, td, headings, lists.
<html><head></head><body>
<p><b>CONTRACT DE DONAŢIE</b></p>
<p>Între subsemnaţii:</p>
…
<table><tr><td><p>DONATORI,<br />IONIŢĂ GRIGORE</p></td></tr></table>
</body></html>
Void elements are written self-closing (<br />), everything else gets a full end tag (<p></p>),
so a browser reads the result the same way an XML parser does.
The two switches compose: add filterOutput: true to also squeeze the whitespace and apply the
whitelists to what is left.
Character encoding
A Word export is written in the ANSI code page of the machine that saved it — usually
windows-1252 — and says so in a meta element. The file overloads honour that declaration
(and a byte order mark before it), falling back to UTF-8, so accented text survives:
// <meta http-equiv=Content-Type content="text/html; charset=windows-1252">
CleanerHelper.Instance.GenerateXHtml(inputFile, outputFile, cleanWordMarkup: true);
// → "cetăţean român", not "cet??ean rom?n"
This applies to the plain path too. The string and reader overloads take text you have already decoded, so the encoding is yours to pick there.
Tuning the Word clean up
The rules live on WordHtmlWriter as plain lists, so they can be retargeted at another producer:
| Member | Default |
|---|---|
DiscardedElements |
meta link style script base basefont xml — dropped with their content |
UnwrappedElements |
span font div — dropped, children kept |
VoidElements |
area base basefont br col embed frame hr img input isindex link meta param source track wbr |
KeepAttributes |
false |
KeepComments |
false |
var builder = new StringBuilder();
using (var reader = new HtmlReader(wordHtml))
using (var writer = new WordHtmlWriter(builder))
{
writer.KeepAttributes = true; // keep href and src
writer.UnwrappedElements = new[] { "span", "font" }; // but keep the divs
writer.WriteCleanedDocument(reader);
}
Parsing without cleaning
SgmlReaderHelper is the thin wrapper over the SGML reader, with none of the HtmlCleaner
filtering — it only balances the document. Use it when you want the HTML repaired but nothing
removed:
string xml = new SgmlReaderHelper().ProcessString("<o:p>noise</o:p><p>keep</p>");
// <html><o:p xmlns:o="#unknown">noise</o:p><p>keep</p></html>
Unknown prefixes are bound to a placeholder namespace so that the result is still well-formed XML.
Types
| Type | Purpose |
|---|---|
HtmlCleaner.CleanerHelper |
Static entry point; Instance returns the shared ICleanerHelper. |
HtmlCleaner.Interfaces.ICleanerHelper |
The three GenerateXHtml overloads, for mocking. |
HtmlCleaner.Text.HtmlReader |
SgmlReader which skips prefixed elements. |
HtmlCleaner.Text.HtmlWriter |
XmlTextWriter which escapes text and applies the whitelists. |
HtmlCleaner.Text.WordHtmlWriter |
HtmlWriter which removes the Word export scaffolding. |
HtmlCleaner.SgmlReaderHelper |
Balance-only helper, no filtering. |
Notes
GenerateXHtmlthrowsArgumentNullExceptionfor a null reader or writer, and the usualSystem.IOexceptions (FileNotFoundException,DirectoryNotFoundException, …) for the file overload.- The instance behind
CleanerHelper.Instanceis a stateless singleton and is safe to share; a singleHtmlReader/HtmlWriterpair, like anyXmlReader/XmlWriter, is not thread-safe. - Processing is logged at info level through
CoreLibrary.Logging.
License
Copyright (c) Piero Viano. All rights reserved.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net45 is compatible. net451 was computed. net452 was computed. net46 was computed. net461 is compatible. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.5
- Microsoft.Xml.SgmlReader (>= 1.8.25)
- Net4x.AsyncBridge (>= 1.5.0.26243)
- Net4x.CoreLibrary.Base (>= 2.5.0.26236)
- Net4x.CoreLibrary.Classes (>= 2.5.0.26236)
- Net4x.CoreLibrary.Interfaces.Configuration (>= 2.5.0.26236)
- Net4x.CoreLibrary.Interfaces.Logging (>= 2.5.0.26236)
- Net4x.CoreLibrary.Logging (>= 2.5.0.26236)
- Net4x.CoreLibrary.Threads (>= 2.5.0.26236)
-
.NETFramework 4.6.1
- Microsoft.Xml.SgmlReader (>= 1.8.25)
- Net4x.AsyncBridge (>= 1.5.0.26243)
- Net4x.CoreLibrary.Base (>= 2.5.0.26236)
- Net4x.CoreLibrary.Classes (>= 2.5.0.26236)
- Net4x.CoreLibrary.Interfaces.Configuration (>= 2.5.0.26236)
- Net4x.CoreLibrary.Interfaces.Logging (>= 2.5.0.26236)
- Net4x.CoreLibrary.Logging (>= 2.5.0.26236)
- Net4x.CoreLibrary.Threads (>= 2.5.0.26236)
-
.NETStandard 2.0
- Microsoft.Xml.SgmlReader (>= 1.8.25)
- Net4x.AsyncBridge (>= 1.5.0.26243)
- Net4x.CoreLibrary.Base (>= 2.5.0.26236)
- Net4x.CoreLibrary.Classes (>= 2.5.0.26236)
- Net4x.CoreLibrary.Interfaces.Configuration (>= 2.5.0.26236)
- Net4x.CoreLibrary.Interfaces.Logging (>= 2.5.0.26236)
- Net4x.CoreLibrary.Logging (>= 2.5.0.26236)
- Net4x.CoreLibrary.Threads (>= 2.5.0.26236)
- System.Text.Encoding.CodePages (>= 9.0.0)
NuGet packages (5)
Showing the top 5 NuGet packages that depend on Net4x.HtmlCleaner:
| Package | Downloads |
|---|---|
|
Net4x.CefSharp.Library
Package Description |
|
|
Net4x.CefSharp.2012.Library.x64
Package Description |
|
|
Net4x.CefSharp.2012.Library.x86
Package Description |
|
|
Net4x.Browser2012Library
Package Description |
|
|
Net4x.BrowserLibrary
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.4.0.26249 | 51 | 9/6/2026 |
| 1.4.0 | 683 | 3/31/2025 |
| 1.1.0 | 505 | 8/27/2023 |
| 1.1.0-at20230506042041 | 278 | 5/7/2023 |