Mojibake 0.1.1

dotnet add package Mojibake --version 0.1.1
                    
NuGet\Install-Package Mojibake -Version 0.1.1
                    
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="Mojibake" Version="0.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Mojibake" Version="0.1.1" />
                    
Directory.Packages.props
<PackageReference Include="Mojibake" />
                    
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 Mojibake --version 0.1.1
                    
#r "nuget: Mojibake, 0.1.1"
                    
#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 Mojibake@0.1.1
                    
#: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=Mojibake&version=0.1.1
                    
Install as a Cake Addin
#tool nuget:?package=Mojibake&version=0.1.1
                    
Install as a Cake Tool

Mojibake

Detects and repairs mojibake: text that was garbled by an encoding mistake.

café        ->  café
résumé     ->  résumé
El Niño     ->  El Niño
—          ->  —
30€        ->  30€
😊          ->  😊

(Every pair above is the actual input and output, verified against ftfy 6.3.1.)

using Mojibake;

string clean = Text.Fix("café");   // "café"

Mojibake is a focused .NET port of the core of Python's ftfy (fixes text for you) fix_encoding algorithm. It is ported verbatim from ftfy 6.3.1. On the test corpus, the repaired text and the step plan match ftfy 6.3.1 exactly: Text.Fix returns what ftfy returns, and Text.FixAndExplain reports the same step plan as ftfy.fix_encoding_and_explain. Text.Badness and Text.LooksLikeMojibake match ftfy exactly for BMP text; they can under-count when a non-BMP (astral-plane) letter or number sits immediately adjacent to a mojibake signature, because .NET regex matches on UTF-16 code units while Python matches on code points. This affects only the detection score, never repair. Full code-point-aware badness for astral input is planned for a future release. Mojibake adds one deliberate, safer deviation: a no-worsening guarantee (see below), so on the rare (theoretical) deeply multiply-encoded input where ftfy would emit a higher-badness partial result, Mojibake returns the input unchanged instead. Text.Fix is therefore parity-or-safer: it returns either exactly what ftfy returns, or, when that would be worse than the input, the input itself, never a third value. Zero external dependencies.

The gap this fills

ftfy is the gold standard for repairing mojibake, with millions of downloads a month, but it is a Python library. On .NET there has been nothing that does its job. UTF.Unknown (the Ude successor) only detects the charset of raw bytes; it cannot repair a string that has already been decoded incorrectly, which is exactly what mojibake is. The two compose cleanly:

  • Use UTF.Unknown to detect and decode unknown bytes into a .NET string.
  • Use Mojibake to repair an already-decoded string that came out garbled.

How mojibake happens

Text is stored as UTF-8 bytes. When a program reads those bytes but assumes they are Windows-1252 or Latin-1 (the most common single-byte encodings), every non-ASCII character comes apart into its individual UTF-8 bytes, each shown as its own Latin-1 character. The é in café (UTF-8 bytes C3 A9) becomes the two characters à and ©, so café reads as café. Smart quotes, dashes, and emoji break the same way, and text can be mangled two or three times over. Repair reverses the mistake: encode the broken string back to bytes with the wrong codec that produced it, then decode those bytes as UTF-8.

The safety contract: repair never makes text worse

The cardinal rule, inherited from ftfy, is that a repair only counts when it lowers the text's "badness" (a heuristic count of mojibake-signature character sequences). If no candidate repair strictly lowers the badness, the input is returned unchanged, and in fact the very same string reference is returned. That makes it safe to run Text.Fix over an entire dataset, including rows that are already clean:

string[] rows = { "café", "élan", "naïve", "menu", "über" };
foreach (string row in rows)
{
    string cleaned = Text.Fix(row);
    // "café" and "naïve" and "menu" come back untouched;
    // "élan" becomes "élan"; "über" becomes "über".
}

What it fixes

The default (FixOptions.Default) runs the safe, high-value pair:

Fixer Default What it does
FixEncoding on The round-trip repair: the whole point of the library.
FixC1Controls on Turns leftover C1 control characters (U+0080 to U+009F) into their Windows-1252 punctuation, as browsers do.
RemoveTerminalEscapes off Strips ANSI terminal escape sequences (color codes).
FixLatinLigatures off Breaks Latin ligatures such as the fi ligature into their letters.
UnescapeHtml off Decodes unambiguous HTML entities such as &amp;.
NormalizeNfc off Applies Unicode NFC normalization.

The encoding repair itself is the full ftfy pipeline: the sloppy Windows-1252, 1251, 1250, 1253, 1254, and 1257 codecs, Latin-1, ISO-8859-2, MacRoman and CP437, plus restore_byte_a0, replace_lossy_sequences, decode_inconsistent_utf8, the utf-8-variants (CESU-8) decoder, and a fixed-point loop for multiply-encoded text.

string input = "café";

var options = new FixOptions { RemoveTerminalEscapes = true, NormalizeNfc = true };
string cleaned = Text.Fix(input, options);

// Or repair the encoding and nothing else:
string repaired = Text.Fix(input, FixOptions.EncodingOnly);

Auditing with FixAndExplain

FixAndExplain returns the repaired text plus the exact plan that produced it, mirroring ftfy's explain feature. Each step names a codec or a fixer.

FixResult result = Text.FixAndExplain("étude");

// result.Text            == "étude"
// result.Changed         == true
// result.OriginalBadness  > result.FixedBadness
// result.Steps           == [ (encode, latin-1), (decode, utf-8) ]

Detecting before fixing

Score text or flag suspicious rows without changing anything.

bool looksBroken = Text.LooksLikeMojibake("étude");   // true
double score     = Text.Badness("étude");             // greater than 0

string[] rows = { "café", "élan", "naïve" };
var suspicious = rows.Where(Text.LooksLikeMojibake).ToList();   // [ "élan" ]

Input handling

  • null input throws ArgumentNullException.
  • Empty input returns empty.
  • No method ever throws on its text input, and repair never returns text worse than the input.
  • Every method is stateless and thread-safe.

Performance

Repairing a typical short string takes on the order of ten microseconds (roughly 100,000 strings a second on a mixed corpus), and scoring badness a couple of microseconds. The heuristic regexes are linear in practice, with no catastrophic backtracking: a 200,000-character adversarial string of repeated mojibake repairs in about 0.2 seconds, and a 10 MB input in a few seconds. This is roughly four times slower than ftfy on small pathological inputs, which is not a concern at these speeds.

Scope

In scope for 0.1.0 is the encoding-repair core of ftfy plus the C1 control fixer by default, with a few adjacent fixers available as opt-in switches. Out of scope, and left for a later release, are the stylistic ftfy fixers (uncurl quotes, fix character width), first-class Unicode normalization beyond opt-in NFC, and the ftfy guess_bytes fallback for raw bytes of unknown encoding. Mojibake is not a charset detector for raw bytes; for that, reach for UTF.Unknown, then repair the result here.

Roadmap

  • More of ftfy's fixers (uncurl quotes, fix character width).
  • NFC and other normalization forms as first-class options.
  • A guess-bytes helper for the raw-bytes case.

License

MIT. Copyright (c) 2026 Israel Iyonsi.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.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
0.1.1 96 8/7/2026
0.1.0 90 8/6/2026