ProvenPin Install

ftfy mojibake repair and curly-quote normalization: working versions (tested 2026-10-16)

Repair Latin-1 mojibake with ftfy, undo double-encoding, straighten curly quotes to ASCII and control HTML unescaping, all tested offline under pytest.

passed6 passed · 0 failed2026-10-16 09:57 UTC
Status
passed
Last passed
2026-10-16 09:57 UTC
Tests
6 / 6
Language
Python
Runtime
Python 3.13.16
pinned versions2
ftfy@6.3.1pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 38883899161

The code

Exactly the files that passed. Your agent can pull them with get_recipe("ftfy-mojibake-fix") (install), or see them on GitHub.

requirements.txt
ftfy==6.3.1
pytest==9.1.1
fixer.py
import ftfy
from ftfy import TextFixerConfig, fix_and_explain, fix_text

KEEP_TYPOGRAPHY = TextFixerConfig(uncurl_quotes=False, unescape_html=False)


def clean(text: str) -> str:
    """Fix mojibake and reduce typographic characters to ASCII."""
    return fix_text(text)


def clean_keep_typography(text: str) -> str:
    """Fix mojibake but keep curly quotes and HTML entities as written."""
    return fix_text(text, config=KEEP_TYPOGRAPHY)


def explain(text: str) -> tuple[str, list[str]]:
    """Return the fixed text plus the labels of the fixers that fired."""
    fixed = fix_and_explain(text)
    return fixed.text, [f"{kind}:{name}" for kind, name in fixed.explanation]
tests/test_fixer.py
from fixer import clean, clean_keep_typography, explain


def mojibake(text: str) -> str:
    """Reproduce the classic bug: UTF-8 bytes decoded as Latin-1."""
    return text.encode("utf-8").decode("latin-1")


def test_fixes_single_encoded_mojibake():
    assert mojibake("café") == "café"
    assert clean(mojibake("café")) == "café"
    assert clean("plain ASCII") == "plain ASCII"


def test_fixes_double_encoded_mojibake():
    assert clean(mojibake(mojibake("café"))) == "café"


def test_uncurls_quotes_by_default():
    # fix_text does more than repair encoding: it also straightens quotes.
    assert clean(mojibake("The “smart” quote")) == 'The "smart" quote'


def test_keep_typography_config_preserves_quotes():
    assert clean_keep_typography("“smart”") == "“smart”"


def test_html_entities_unescaped_by_default():
    assert clean("Tom & Jerry") == "Tom & Jerry"
    assert clean_keep_typography("Tom & Jerry") == "Tom & Jerry"


def test_explain_reports_encoding_steps():
    text, steps = explain(mojibake("café"))
    assert text == "café"
    assert steps == ["encode:latin-1", "decode:utf-8"]
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.