文字コードが不明なデータを受け取り処理をする必要があり、そのデータから使用されている文字コードを判定する方法を探していた。Pythonにはchardetという文字コードの判定用のライブラリがある。これを使うと、そのバイト列が何の文字コードでエンコードされているか判定できる。とても便利ではあるけれど、どうやってそれを実現しているのかを知りたくなった。そのため、chardetについて調べる事にする。
https://github.com/chardet/chardet
chardet.universaldetector.UniversalDetector()によって判定は行われる。このクラスのインスタンス属性である result
には {'encoding': None, 'confidence': 0.0, 'language': None}
のような形式のデータが保持される。encodingには文字コードが、confidenceにはその文字コードである可能性が設定される。確実にその文字コードと言い切れる時と、言い切れない時があり、言い切れる時は1.0が設定される。
UTF系の文字コードの場合、先頭のデータを見る事で、判定が▽できるようだ。例えばBOM付きUTF-8の判定は以下のように実施している。
# If the data starts with BOM, we know it is UTF
if byte_str.startswith(codecs.BOM_UTF8):
# EF BB BF UTF-8 with BOM
self.result = {'encoding': "UTF-8-SIG",
'confidence': 1.0,
'language': ''}
CP932のような日本語向けの文字コードは、UniversalDetector()で直接行っている訳ではなく、UniversalDetectorから移譲される形で判定処理が実行される。具体的には次のような呼び出しの構図がある。
UniversalDetector -> MBCSGroupProber -> SJISProber -> SJISContextAnalysis
最終的にはSJISContextAnalysis.feed()によって判定される。
def get_order(self, byte_str):
if not byte_str:
return -1, 1
# find out current char's byte length
first_char = byte_str[0]
if (0x81 <= first_char <= 0x9F) or (0xE0 <= first_char <= 0xFC):
char_len = 2
if (first_char == 0x87) or (0xFA <= first_char <= 0xFC):
self._charset_name = "CP932"
else:
char_len = 1
# return its order if it is hiragana
if len(byte_str) > 1:
second_char = byte_str[1]
if (first_char == 202) and (0x9F <= second_char <= 0xF1):
return second_char - 0x9F, char_len
return -1, char_len
実際に判定をしてみると次のようになる。
>>> import chardet
>>> chardet.detect("日本語".encode("cp932"))
{'encoding': 'Windows-1252', 'confidence': 0.73, 'language': ''}
>>> chardet.detect("日本語".encode("sjis"))
{'encoding': 'Windows-1252', 'confidence': 0.73, 'language': ''}
>>> chardet.detect("日本語".encode("utf8"))
{'encoding': 'utf-8', 'confidence': 0.87625, 'language': ''}
>>> chardet.detect("テスト。".encode("cp932"))
{'encoding': 'SHIFT_JIS', 'confidence': 0.99, 'language': 'Japanese'}
>>> chardet.detect("難しい。".encode("cp932"))
{'encoding': None, 'confidence': 0.0, 'language': None}
>>>
cp932を指定してエンコードした値は、=Windows-1252= であったり SHIFT_JIS
であったり、または判定不能でNoneとなったりするらしい。これは扱いが難しい