############## From chardet ############## .. package-meta:: chardet chardet/chardet `chardet `_ is the pure-Python universal character encoding detector, the tool most projects reach for when they receive undeclared bytes from a file, a socket, or an HTTP body with no reliable ``Content-Type``. ``chardet.detect(data)`` returns an ``{"encoding", "confidence", "language"}`` dict, ``detect_all`` ranks the candidates, and ``UniversalDetector`` accumulates a stream chunk by chunk so a reader can stop as soon as the verdict is settled. It ships an ensemble of probers (escape-sequence, multi-byte, and single-byte frequency models) that vote on the most likely encoding, an approach inherited from Mozilla's original ``universalchardet``. It is a dependency of ``requests`` and countless scrapers and ETL pipelines. This guide also covers `cchardet `_, whose ``cchardet.detect`` is the same call bound to the C ``uchardet`` engine. turbohtml covers the same ground with :func:`turbohtml.detect.detect`, a standalone entry point over the C detector it already ships for :func:`~turbohtml.parse`. It answers "what encoding are these bytes?" without an HTML parser in the call path, and returns a typed :class:`~turbohtml.detect.EncodingMatch` in place of chardet's dict. ********************** turbohtml vs chardet ********************** .. list-table:: :header-rows: 1 :widths: 18 41 41 - - Dimension - turbohtml - chardet - - Scope - WHATWG-conformant detection: a byte-order-mark sniff, an HTML ```` prescan of the first 1024 bytes, then the ``chardetng`` content detector with the spec's windows-1252 fallback -- the algorithm a browser runs on the same bytes. - Ensemble of probers voting on the most likely encoding; ignores HTML markup and never applies a browser's windows-1252 fallback. - - Feature breadth - ``detect``, ``detect_all``, an incremental :class:`~turbohtml.detect.EncodingDetector`, plus a frozen :class:`~turbohtml.detect.Detection` config for confidence floor, language hint, and allow/exclude constraints. - ``detect``, ``detect_all``, ``UniversalDetector`` with a ``lang_filter``; no allow/exclude set, no language hint. - - Performance - ASCII, valid UTF-8, and real web pages short-circuit before any scoring, resolving 40x to 2000x ahead; legacy single-byte text runs about 3x ahead. See the table below. - Prober ensemble runs every model on every input; no fast path for clean UTF-8 or ASCII. - - Typing - Fully typed: ``EncodingMatch``, ``Detection``, ``EncodingDetector`` are annotated dataclasses/classes with stubs. - Returns untyped ``dict``; type stubs are third-party. - - Dependencies - None beyond the turbohtml C extension. - Pure Python, no dependencies (``cchardet`` needs a C build). - - Maintenance - Actively developed alongside the parser; the detector is the same code the parser uses in production. - Maintained but slow-moving; the model set has been stable for years. Feature overlap =============== The detection surface ports one-to-one: - ``chardet.detect(data)`` -> :func:`turbohtml.detect.detect`, same three fields as a typed record. - ``chardet.detect_all(data)`` -> :func:`turbohtml.detect.detect_all`, ranked candidates best first. - ``UniversalDetector()`` with ``feed`` / ``close`` / ``reset`` / ``done`` / ``result`` -> :class:`turbohtml.detect.EncodingDetector` with the same five members. - ``UniversalDetector(lang_filter=...)`` -> a :class:`~turbohtml.detect.Detection` ``allowed`` frozenset of the WHATWG encoding names to keep. - chardet's implicit 0.2 minimum confidence -> :meth:`Detection.chardet() `. - ``cchardet.detect(data)`` (including the maintained ``faust-cchardet`` fork) -> :func:`turbohtml.detect.detect`. What turbohtml adds =================== - HTML ```` charset awareness: a declaration in the first 1024 bytes wins per the WHATWG prescan, so detection agrees with what the browser and :func:`turbohtml.parse` would decode. chardet and cchardet ignore markup entirely. - Browser-faithful results: turbohtml reports the WHATWG canonical encoding a browser would pick, where chardet often names a sibling or superset (ISO-8859-7 for Greek windows-1253 bytes, GB18030 for GBK). - A frozen :class:`~turbohtml.detect.Detection` config with a language hint, an ``allowed`` set, and a mutually exclusive ``excluded`` set -- richer than chardet's single ``lang_filter``. - Fully typed results: :class:`~turbohtml.detect.EncodingMatch` instead of an untyped dict. - One detection path shared with parsing: standalone ``detect`` and ``parse(detect_encoding=True)`` always agree on the same bytes. What chardet has that turbohtml does not ======================================== - A wider candidate set. turbohtml scores chardetng's list: UTF-8, ISO-2022-JP, five CJK encodings, and 19 single-byte encodings. chardet's extras outside that set (UTF-16/32 *without* a byte-order mark, MacCyrillic, TIS-620, Johab) resolve to the closest WHATWG candidate instead. No equivalent when you need one of those exact labels. A UTF-16 or UTF-32 stream that *does* carry a mark now reports its exact label (see below). - A raw CJK speed edge on the C fork. On CJK-heavy byte streams (the Shift_JIS row), ``cchardet``'s uchardet engine outruns turbohtml, whose CJK scoring drives a CPython incremental codec per candidate. Workaround: keep ``faust-cchardet`` for that one workload if it dominates; turbohtml leads on every other row. Performance =========== .. bench-table:: :file: bench/chardet.json Certain input short-circuits before any scoring, so ASCII, valid UTF-8, and real web pages resolve 40x to 2000x ahead of chardet's prober ensemble; declaration-less legacy single-byte text still runs about 3x ahead. Both libraries decode a 15-sample multilingual differential correctly, though chardet often names a sibling or superset where turbohtml reports the WHATWG encoding a browser would pick. The one exception is CJK-heavy bytes, where ``cchardet``'s uchardet engine leads (the Shift_JIS row, 22x); turbohtml leads on the other rows. **************** How to migrate **************** Swap the import and read the fields off the typed record instead of the dict: .. list-table:: :header-rows: 1 :widths: 50 50 - - `chardet `__ - turbohtml - - ``chardet.detect(data)`` - :func:`~turbohtml.detect.detect` - - ``chardet.detect_all(data)`` - :func:`~turbohtml.detect.detect_all` - - ``UniversalDetector()`` / ``feed`` / ``close`` / ``reset`` / ``done`` / ``result`` - :class:`~turbohtml.detect.EncodingDetector` with the same five members - - ``UniversalDetector(lang_filter=LanguageFilter.CJK)`` - ``Detection(allowed=frozenset({"gbk", "big5", "shift_jis", "euc-jp", "iso-2022-jp", "euc-kr"}))`` - - the implicit 0.2 minimum confidence - ``Detection.chardet()`` - - ``cchardet.detect(data)`` - :func:`~turbohtml.detect.detect` The dict becomes a typed :class:`~turbohtml.detect.EncodingMatch` with the same three fields: .. code-block:: python # chardet import chardet guess = chardet.detect(data) # {"encoding": ..., "confidence": ..., "language": ...} # turbohtml from turbohtml.detect import detect match = detect(data) # EncodingMatch(encoding=..., confidence=..., language=...) .. testcode:: from turbohtml.detect import detect match = detect("Привет мир, как дела".encode("cp1251")) print(match.encoding, match.language) .. testoutput:: windows-1251 Russian The maintained ``cchardet`` story is a footnote: the original package stopped at 2.1.7 (2021) and no longer compiles on Python 3.11+, where the ``longintrepr.h`` header it includes left the public C API. The fork `faust-cchardet `_ keeps the ``import cchardet`` name alive. Both expose only ``detect`` and a ``UniversalDetector`` without ``detect_all``, and neither reports a language; the turbohtml calls above replace either package unchanged. ********************** Gotchas and pitfalls ********************** - Encoding names differ in spelling, not identity: turbohtml reports the WHATWG canonical name (``windows-1251``, ``Shift_JIS``), chardet its own casing (``Windows-1251``, ``CP932``), cchardet upper case. Every name turbohtml can detect is a valid :mod:`python:codecs` alias, so ``data.decode(match.encoding)`` works; only a ````-declared ``x-user-defined`` has no stdlib codec. - Confidence scales are not comparable across libraries. turbohtml's confidence is the candidate's share of the positive frequency scores (1.0 for a declaration or structural proof, 0.0 for the no-evidence windows-1252 fallback); do not port a chardet threshold number directly, use :meth:`Detection.chardet() ` for its 0.2 floor. - turbohtml honors an HTML ```` charset declaration in the first 1024 bytes, per the WHATWG prescan; chardet and cchardet ignore markup. Feed :class:`~turbohtml.detect.Detection` ``excluded`` constraints instead of re-sniffing when a declaration is known to lie. - A byte-order mark reports the mark's own label and sets ``EncodingMatch.bom``: a UTF-8 mark comes back as ``UTF-8-SIG`` (chardet's spelling), and the UTF-16 and UTF-32 marks as ``UTF-16LE`` / ``UTF-16BE`` / ``UTF-32LE`` / ``UTF-32BE``. Decode with the matching codec (``utf-8-sig``, ``utf-16``, ``utf-32``) to strip the mark. The spec-locked :func:`~turbohtml.parse` sniff is unaffected -- it keeps the plain WHATWG name and treats ``FF FE 00 00`` as UTF-16LE, so ``detect`` and ``parse(detect_encoding=True)`` agree on every input except a marked one. - The candidate set is chardetng's: UTF-8, ISO-2022-JP, five CJK encodings, and 19 single-byte encodings. chardet's extras outside that set (UTF-16/32 *without* a mark, MacCyrillic, TIS-620, Johab) resolve to the closest WHATWG candidate instead.