__TOC__

Питання: що є причиною №1 кракозябрів в інтернеті, в вашій поштовій скринці, і в кожній комп’ютерній системі яку ви писали? Це кодування символів. В розділі Текст, я розповідав про історію кодувань та створення Юнікоду, "одного кодування щоб керувати ними всіма". Я б був щасливий ніколи більше не бачити кракозябрів на екрані, тому що всі системи створення тексту зберігали б точну інформацію про кодування, всі протоколи передачі мали уявлення про Юнікод, і кожна система що працює з текстом зберігала б ідеальну якість при перетвореннях між різними кодуваннями.

Мені також сподобалось би поні.

Юнікодове поні.

Юніпоні, так би мовити.

Але я задовольнюсь автовизначенням кодування символів.

Що таке автовизначення кодування символів? Це означає взяти послідовність байт в невідомому кодуванні, та спробувати визначити кодування, аби мати змогу прочитати текст. Це як зламування шифру, коли у вас немає ключа.

Хіба це не неможливо? Взагалі, так. Щоправда, деякі кодування оптимізовані для деяких мов, а мови не випадкові. Деякі послідовності символів з’являються постійно, в той час як інші не мають жодного сенсу. Людина яка вільно читає англійською, відкривши газету і побачивши там "txzqJv 2!dasd0a QqdKjvz" одразу зрозуміє що це не англійська (хоча рядок і складений цілком з латинських літер). Вивчаючи багато "типового" тексту, комп’ютерний алгоритм може імітувати подібний рівень "вільного володіння" і вгадувати мову тексту.

Іншими словами, визначення кодування, це насправді визначення мови, комбіноване з знанням того які мови використовують які кодування.

А такий алгоритм існує? Як виявилось, так. Всі основні браузери мають автовизначення кодування символів, тому що в інтернеті повно сторінок які взагалі не містять інформації про кодування. Mozilla Firefox містить бібліотеку автовизначення кодування яка поширюється з відкритим кодом. Я портував ту бібліотеку на Python 2, як модуль chardet. В цьому розділі ми крок за кроком пройдемось кріз процес портування модуля chardet з Python 2 на Python 3.

Представляєю модуль chardet Перед тим як ми почнемо переносити код, було б добре розібратись у тому як він працює. Це корокта довідка щодо того як читати код. Бібліотека chardet занадто велика щоб вставити її код прямо сюди, але ви можете прочитати її код на GitHub.

Головною вхідною точкою алгоритму визначення є universaldetector.py, який має один клас, UniversalDetector. (Ви можете думати що головною вхідною точкою є функція detect в файлі chardet/__init__.py, але насправді це просто функція для зручності, яка створює об’єкт UniversalDetector, викликає його, та повертає результат виклику.)

UniversalDetector працює з 5 видами кодувань:

UTF-N з маркером порядку байтів (BOM). Це включає UTF-8, як Big-Endian так і Little-Endian варіанти UTF-16, та всі чотирибайтові варіанти UTF-32. Екрановані кодування, які повністю сумісні з семибітним ASCII, де не-ASCII символи починаються з символу екранування. Приклади: ISO-2022-JP (Японська) та HZ-GB-2312 (Китайська). Багатобайтові кодування, в яких кожен символ представлено різною кількістю байтів. Приклади: BIG5 (Китайська), SHIFT_JIS (Японська), EUC-KR (Корейська), та UTF-8 без BOM. Однобайтові кодування, в яких кожен символ представлений одним байтом. Приклади: KOI8-R (Російська), WINDOWS-1255 (Єврейська), та TIS-620 (Тайська). WINDOWS-1251, яка використовується переважно на Microsoft Windows менеджерами середньої ланки, які не відрізнять кодування символів від власної дупи.

UTF-8 з BOM Якщо текст починається з BOM, ми маємо причину припускати що текст закодований в UTF-8, UTF-16, чи UTF-32. (BOM вкаже нам точно яка з них, вона якраз для цього призначена.) Цей випадок опрацьовується прямо в UniversalDetector, який негайно поверне результат, без подальшої обробки.

Екрановані кодування Якщо текст містить впізнавану ескейп-послідовність, це може вказувати що використано екрановане кодування. UniversalDetector створить EscCharSetProber (описаний в escprober.py) та передасть йому текст.

EscCharSetProber створює послідовність скінченних автоматів, які базуються на основі моделей кодувань HZ-GB-2312, ISO-2022-CN, ISO-2022-JP, та ISO-2022-KR (описаних в escsm.py). EscCharSetProber передає текст в кожен з цих скінченних автоматів, по одному байту за раз. Якщо будь-який з цих скінченних автоматів зупиниться, унікально ідентифікуючи кодування, EscCharSetProber негайно поверне позитивний результат в UniversalDetector, який поверне результат тому хто викликав цей об’єкт. Якщо будь-який з скінченних автоматів зустрічає недозволену послідовність, вона викидається і обробка продовжується з іншими скінченними автоматами.

Багатобайтові кодування

Припускаючи відсутність BOM, UniversalDetector перевіряє чи містить текст будь-які символи, з найбільш значущим бітом в байті. Якщо так, він створює послідовність "проберів" для визначення багатобайтових кодувань, однобайтових кодувань, і як крайній вихід - windows-1252.

Пробер багатобайтових кодувань, MBCSGroupProber (описаний в mbcsgroupprober.py), є просто оболонкою яка керує групою інших проберів, по одному на кожне багатобайтове кодування: Big5, GB2312, EUC-TW, EUC-KR, EUC-JP, SHIFT_JIS, та UTF-8. MBCSGroupProber передає текст кожному з цих проберів та перевіряє результати. Якщо пробер звітує про те що він знайшов недозволену послідовність байтів, він видаляється з подальшої обробки (так, що наприклад, будь-які подальші виклики UniversalDetector.feed() пропустять той пробер. Якщо пробер звітує що він достатньо впевнений в тому що визначив кодування, MBCSGroupProber звітує про цей позитивний результат в UniversalDetector, а той передає його далі.

Більшість проберів багатобайтових кодувань наслідуються від MultiByteCharSetProber (описаний в mbcharsetprober.py), і просто підтягують відповідний скінченний автомат, та аналізатор розподілу і дозволяють MultiByteCharSetProber зробити решту роботи. MultiByteCharSetProber пропускає текст через відповідні кодуванням скінченні автомати, по байту за раз, щоб дізнатись про послідовності байтів, які вкажуть на заключний позитивний чи негативний результат. В той же час, MultiByteCharSetProber передасть текст відповідному до кодування аналізатору розподілу.

Аналізатори розподілу (визначені в chardistribution.py) використовують мовно-специфічні моделі того які символи використовуються найчастіше. Як тільки MultiByteCharSetProber передасть достатньо тексту аналізатору розпоідлу, він обчислює коефіцієнт впевненості, який базується на кількостях часто використовуваних символів, загальному числу символів, та залежній від мови ймовірності розподілу символів. Якщо рівень впевненості достатньо високий, MultiByteCharSetProber поверне результат в MBCSGroupProber, який поверне його в UniversalDetector а той вже передасть його далі.

Випадок Японської більш складний. Односимвольний аналіз розподілу не завжди є достатнім для того аби відрізнити EUC-JP та SHIFT_JIS, тому SJISProber (описаний в sjisprober.py) також використовує аналіз розподілу пар символів. SJISContextAnalysis та EUCJPContextAnalysis (обоє описані в jpcntx.py та обоє наслідуються від спільного класу JapaneseContextAnalysis), перевіряють частоту складових символів Хірагани в тексті. Як тільки текст буде опрацьовано, вони повертають рівень впевненості в SJISProber, який перевіряє обидва аналізатори і повертає результат того, чий рівень впевненості буде вищим в MBCSGroupProber.

Однобайтові кодування

Пробер однобайтових кодувань, SBCSGroupProber (описаний в sbcsgroupprober.py), також є лише оболонкою яка керує групою інших проберів, по одному для кожної комбінації однобайтових кодувань та мов: windows-1251, KOI8-R, ISO-8859-5, MacCyrillic, IBM855, та IBM866 (Російська); ISO-8859-7 та windows-1253 (Грецька); ISO-8859-5 та windows-1251 (Болгарська); ISO-8859-2 та windows-1250 (Угорська); TIS-620 (Тайська); windows-1255 та ISO-8859-8 (Іврит).

SBCSGroupProber передає текст кожному з цих специфічних для мови та кодування проберів і перевіряє результати. Всі ці пробери реалізовані в єдиному класі, SingleByteCharSetProber (описаному в sbcharsetprober.py), який приймає модель мови як аргумент. Модель мови описує як часто різні двосимвольні послідовності з’являються в типовому тексті. SingleByteCharSetProber опрацьовує текст, і відмічає найчастіше вживані двосимвольні послідовності. Як тільки текст було опрацьовано, він обчислює рівень впевненості, базуючись на кількостях частовживаних послідовностей, загальній кількості символів та унікальному для мови розподілу частоти.

Іврит опрацьовується окремим випадком. Якщо текст схожий на іврит, за даними аналізу розподілу двосимвольних послідовностей, HebrewProber (описаний в hebrewprober.py) спробує відрізнити між візуальним івритом (де текст справді зберігається "задом наперед" рядок за рядком, а потім так і відображається аби його можна було читати справа наліво) та логічним івритом (де текст зберігається в порядку читання, а потім відображається з права наліво програмою). Тому що деякі символи кодуються по різному залежно від того чи знаходяться вони в середині чи наприкінці слова, ми можемо обґрунтовано вгадати напрямок тексту, і повернути відповідне кодування (windows-1255 для логічного івриту, та ISO-8859-8 для візуального івриту).

windows-1252 Якщо UniversalDetector виявляє в тексті символ з останнім значущим бітом, але жодне з інших багатобайтових кодувань не підійшло, він створює Latin1Prober (описаний в latin1prober.py) щоб спробувати виявити англомовний текст в кодуванні windows-1252. Ця перевірка страшенно ненадійна, тому що латинські букви кодуються однаково в багатьох кодуваннях. Єдиний спосіб відрізнити windows-1252 - за допомогою часто використовуваних символів, таких як лапки, апострофи, символ копірайту, і т.п. Latin1Prober автоматично зменшує коефіцієнт впевненості, аби дати можливість більш точним проберам виграти, якщо це взагалі можливо.

Запуск 2to3 Ми збираємось портувати модуль chardet з Python 2 на Python 3. Python 3 поставляється з корисним скриптом, який називається 2to3, який бере на вхід код на Python 2 та автоматично перетворює його на Python 3. В багатьох випадках це легко - коли функція була перейменована чи переміщена в інший модуль, але в інших випадках все може ставати досить складним. Щоб отримати відчуття того всього що він може зробити, зверніться до додатку Poring code to Python 3 with 2to3. В цьому розділі ми почнемо з запуску 2to3 над пакетом chardet, але як ви побачите, після того як автоматичні інструменти здійснять свою магію, залишиться все ще досить роботи.

Головний пакунок chardet поділений між кількома різними файлами, що знаходяться в одній директорії. Скрипт 2to3 досить просто конвертує багато файлів за раз: просто передайте в якості аргумента командного рядка директорію, і 2to3 опрацює кожен файл всередині.

C:\home\chardet> python c:\Python30\Tools\Scripts\2to3.py -w chardet\ RefactoringTool: Skipping implicit fixer: buffer RefactoringTool: Skipping implicit fixer: idioms RefactoringTool: Skipping implicit fixer: set_literal RefactoringTool: Skipping implicit fixer: ws_comma --- chardet\__init__.py (original) +++ chardet\__init__.py (refactored) @@ -18,7 +18,7 @@ __version__ = "1.0.1"

def detect(aBuf): - import universaldetector + from . import universaldetector u = universaldetector.UniversalDetector() u.reset() u.feed(aBuf) --- chardet\big5prober.py (original) +++ chardet\big5prober.py (refactored) @@ -25,10 +25,10 @@ # 02110-1301 USA ######################### END LICENSE BLOCK #########################

-from mbcharsetprober import MultiByteCharSetProber -from codingstatemachine import CodingStateMachine -from chardistribution import Big5DistributionAnalysis -from mbcssm import Big5SMModel +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import Big5DistributionAnalysis +from .mbcssm import Big5SMModel

class Big5Prober(MultiByteCharSetProber): def __init__(self):