В прошлой статье был реализован алгоритм автоматического определения кодировки текста на основе частот распределения символов. В комментариях отметили: если использовать биграммы (триграммы), результат будет более точный. Тогда я отмахнулся, мол, и на одиночных символах неплохой результат получается. Но сейчас подумал, что неплохо было бы добавить надежности и точности в алгоритм, тем более использование биграмм вместо одиночных символов сильно кушать не просит.
Под катом — пример реализации алгоритма на биграммах, исходники и результаты его работы.
Как всегда — будем работать только с однобайтовыми русскими кодировками. Для определения UTF-8 смысла писать такой алгоритм нету: она определяется очень просто:
Итак, берем достаточно большой русский текст и замеряем вместо частот букв частоты пар букв (я традиционно взял Войну и Мир). Получаем что-то в этом роде (тут показаны не частоты, а кол-во упоминаний в тексте, что фактически одно и то же):
Далее конвертируем это дело во все нужные кодировки, при этом добавляем еще варианты с разным регистром символов (заодно сконвертируем кол-во упоминаний в частоту):
Таких файликов у меня получилось три — по одному на каждую кодировку: cp1251, koi8-r, iso8859-5.
Теперь, когда нам нужно узнать кодировку неизвестного текста, мы проходимся по нему, вычленяем все пары символов и прибавляем к «весу» каждой кодировки частоту этой пары символов по уже сгенерированным спектрам.
Закомментированные массивы — суммарные весы пар символов в тексте для соответствующей кодировки, поделенные на сумму для всех подировок (см. test_detect_encoding.php:21). Т.е. можно сказать, что это вероятности того, что текст именно в этой кодировке.
Как видим, получается очень даже неплохой результат — даже на маленьких строках верная кодировка лидирует почти на порядок.
Все исходники со сгенерированными спектрами для трех однобайтовых кодировок (конечно, для русского языка) можно скачать на гитхабе. Я намеренно не оформлял все это дело в виде реюзабельной либы, потому что там кода-то всего на сотню строк. Если кому будет необходимо, он сможет сам для себя оформить как ему нравится и как навязывает тот или иной используемый фреймворк.
Под катом — пример реализации алгоритма на биграммах, исходники и результаты его работы.
Описание алгоритма
Как всегда — будем работать только с однобайтовыми русскими кодировками. Для определения UTF-8 смысла писать такой алгоритм нету: она определяется очень просто:
$str_utf8 = 'Русский текст'; $str_cp1251 = iconv('UTF-8', 'Windows-1251', $str_utf8); var_dump(preg_match('#.#u', $str_utf8)); var_dump(preg_match('#.#u', $str_cp1251));
m00t@m00t:~/workspace/test$ php detect_encoding.php int(1) int(0)
Итак, берем достаточно большой русский текст и замеряем вместо частот букв частоты пар букв (я традиционно взял Войну и Мир). Получаем что-то в этом роде (тут показаны не частоты, а кол-во упоминаний в тексте, что фактически одно и то же):
<?php return array ( 'аа' => 3, 'аб' => 1127, 'ав' => 5595, 'аг' => 1373, 'ад' => 3572, 'ае' => 1483, 'аё' => 0, 'аж' => 1931, .... 'яс' => 1325, 'ят' => 2439, 'яу' => 1, 'яф' => 1, 'ях' => 284, 'яц' => 70, 'яч' => 254, 'яь' => 0, 'яы' => 0, 'яъ' => 0, 'яэ' => 0, 'яю' => 185, 'яя' => 283, );
Далее конвертируем это дело во все нужные кодировки, при этом добавляем еще варианты с разным регистром символов (заодно сконвертируем кол-во упоминаний в частоту):
<?php return array ( 'аа' => 2.5816978277594E-6, 'Аа' => 2.5816978277594E-6, 'аА' => 2.5816978277594E-6, 'АА' => 2.5816978277594E-6, 'аб' => 0.00096985781729497, 'Аб' => 0.00096985781729497, 'аБ' => 0.00096985781729497, 'АБ' => 0.00096985781729497, 'ав' => 0.0048148664487714, 'Ав' => 0.0048148664487714, 'аВ' => 0.0048148664487714, 'АВ' => 0.0048148664487714, ... 'яы' => 0, 'Яы' => 0, 'яЫ' => 0, 'ЯЫ' => 0, 'яъ' => 0, 'Яъ' => 0, 'яЪ' => 0, 'ЯЪ' => 0, 'яэ' => 0, 'Яэ' => 0, 'яЭ' => 0, 'ЯЭ' => 0, 'яю' => 0.0001592046993785, 'Яю' => 0.0001592046993785, 'яЮ' => 0.0001592046993785, 'ЯЮ' => 0.0001592046993785, 'яя' => 0.00024354016175197, 'Яя' => 0.00024354016175197, 'яЯ' => 0.00024354016175197, 'ЯЯ' => 0.00024354016175197, );
Таких файликов у меня получилось три — по одному на каждую кодировку: cp1251, koi8-r, iso8859-5.
Теперь, когда нам нужно узнать кодировку неизвестного текста, мы проходимся по нему, вычленяем все пары символов и прибавляем к «весу» каждой кодировки частоту этой пары символов по уже сгенерированным спектрам.
Результаты работы
Закомментированные массивы — суммарные весы пар символов в тексте для соответствующей кодировки, поделенные на сумму для всех подировок (см. test_detect_encoding.php:21). Т.е. можно сказать, что это вероятности того, что текст именно в этой кодировке.
$data = iconv('UTF-8', 'iso8859-5', 'Короткая русская строка'); /* array(3) { ["windows-1251"]=> float(0.071131587263965) ["koi8-r"]=> float(0.19145038318717) ["iso8859-5"]=> float(0.73741802954887) } */ $data = iconv('UTF-8', 'windows-1251', 'Короткая русская строка'); /* array(3) { ["windows-1251"]=> float(0.95440659551352) ["koi8-r"]=> float(0.044353550201316) ["iso8859-5"]=> float(0.0012398542851665) } */ $data = file_get_contents('test/cp1251_1.html'); // это какая-то сохраненная страничка в cp1251 /* array(3) { ["windows-1251"]=> float(0.78542385878465) ["koi8-r"]=> float(0.18514302234077) ["iso8859-5"]=> float(0.029433118874583) } */
Как видим, получается очень даже неплохой результат — даже на маленьких строках верная кодировка лидирует почти на порядок.
Все исходники со сгенерированными спектрами для трех однобайтовых кодировок (конечно, для русского языка) можно скачать на гитхабе. Я намеренно не оформлял все это дело в виде реюзабельной либы, потому что там кода-то всего на сотню строк. Если кому будет необходимо, он сможет сам для себя оформить как ему нравится и как навязывает тот или иной используемый фреймворк.
