В прошлой статье был реализован алгоритм автоматического определения кодировки текста на основе частот распределения символов. В комментариях отметили: если использовать биграммы (триграммы), результат будет более точный. Тогда я отмахнулся, мол, и на одиночных символах неплохой результат получается. Но сейчас подумал, что неплохо было бы добавить надежности и точности в алгоритм, тем более использование биграмм вместо одиночных символов сильно кушать не просит.
Под катом — пример реализации алгоритма на биграммах, исходники и результаты его работы.
Как всегда — будем работать только с однобайтовыми русскими кодировками. Для определения 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)
}
*/
Как видим, получается очень даже неплохой результат — даже на маленьких строках верная кодировка лидирует почти на порядок.
Все исходники со сгенерированными спектрами для трех однобайтовых кодировок (конечно, для русского языка) можно скачать на гитхабе. Я намеренно не оформлял все это дело в виде реюзабельной либы, потому что там кода-то всего на сотню строк. Если кому будет необходимо, он сможет сам для себя оформить как ему нравится и как навязывает тот или иной используемый фреймворк.