Handle character encodings¶
Decode bytes of unknown or declared encoding the way a browser would, and inspect what the sniffer chose, with
turbohtml.parse() and turbohtml.detect.detect().
Parse bytes of an unknown encoding¶
turbohtml.parse() accepts bytes and runs the WHATWG encoding sniffing algorithm (a byte-order mark, then a
<meta> declaration, defaulting to windows-1252). Pass encoding to override the sniff, and read
encoding for the WHATWG name that was resolved:
import turbohtml
doc = turbohtml.parse(b'<meta charset="iso-8859-2"><p>\xe1</p>')
print(doc.encoding)
print(doc.find("p").text)
ISO-8859-2
á
Detect an encoding without parsing¶
When you only need the encoding, say to decode a file or a response body, run the same sniff standalone with
turbohtml.detect.detect(); it replaces chardet.detect and charset_normalizer.from_bytes:
from turbohtml.detect import detect
raw = "Précédemment, la créativité française".encode("cp1252")
match = detect(raw)
print(match.encoding, match.language)
print(raw.decode(match.codec))
windows-1252 None
Précédemment, la créativité française
Decode through match.codec, not match.encoding. The two are different strings: encoding is the WHATWG name,
and the CPython codec that answers to the same name is a different encoding – bytes.decode("big5") reaches a strict
subset of the spec’s Big5, koi8-u reaches KOI8-U where the spec means KOI8-RU, and x-mac-cyrillic reaches no
codec at all. match.codec names a whatwg-* codec turbohtml.detect registers, whose decoder is the one
turbohtml.parse() uses, so the text you get back is the text the parser would have seen.
Rank the alternatives with detect_all(), constrain or threshold them with a
Detection config, and feed a stream chunk by chunk with a
EncodingDetector:
from io import BytesIO
from turbohtml.detect import EncodingDetector
stream = BytesIO("\ufeffstreamed UTF-8 content".encode())
detector = EncodingDetector()
for chunk in iter(lambda: stream.read(4096), b""):
detector.feed(chunk)
if detector.done: # the byte-order mark already decided the stream
break
print(detector.close().encoding)
UTF-8-SIG
Normalize decoded text to a Unicode normalization form¶
Once bytes are decoded, the same visible text can carry different code points – "é" as one character or as an e
plus a combining accent – so equality, search, and deduplication need a normalization pass first.
turbohtml.detect.normalize() runs all four UAX #15 forms in C, the successor to
unicodedata.normalize():
from turbohtml.detect import is_normalized, normalize
composed = "café"
decomposed = "café"
print(composed == decomposed)
print(normalize("NFC", decomposed) == composed)
print(is_normalized("NFC", decomposed))
print(normalize("NFKC", "file") == "file")
False
True
False
True
Use NFC to compare or store user text, NFKC to additionally flatten presentation variants (ligatures,
superscripts, width variants), and the NFD / NFKD forms when you want the fully decomposed representation.
is_normalized() answers the membership question without building the normalized copy.