Скорая Компьютерная Помощь г. Калуга

Полный спектр компьютерных услуг!

Здравствуйте, гость ( Вход | Регистрация )

> Внимание!

  • Вся информация, расположенная в данном и других разделах форума получена из открытых источников (интернет-ресурсы, средства массовой информации, печатные издания и т.п.) и/или добавлена самими пользователями. Администрация форума предоставляет его участникам площадку для общения / размещения файлов / статей и т.п. и не несет ответственности за содержание сообщений, а также за возможное нарушение авторских, смежных и каких-либо иных прав, которое может повлечь за собой информация, содержащаяся в сообщениях.
 
Ответить в эту темуОткрыть новую тему
> PHP / Определение кодировки текста в PHP — обзор существующих решений плюс еще один велосипед
Decker
сообщение 11.11.2010, 20:31
Сообщение #1


Администратор
*****

Группа: Главные администраторы
Сообщений: 14349
Регистрация: 12.10.2007
Из: Twilight Zone
Пользователь №: 1



Столкнулся с задачей — автоопределение кодировки страницы/текста/чего угодно. Задача не нова, и велосипедов понапридумано уже много. В статье небольшой обзор найденного в сети — плюс предложение своего, как мне кажется, достойного решения.




1. Почему не mb_detect_encoding() ?


Если кратко — он не работает.

Давайте смотреть:

// На входе - русский текст в кодировке CP1251
$string = iconv('UTF-8', 'Windows-1251', 'Он подошел к Анне Павловне, поцеловал ее руку, подставив ей свою надушенную и сияющую лысину, и покойно уселся на диване.');

// Посмотрим, что нам выдает md_detect_encoding(). Сначала $strict = FALSE
var_dump(mb_detect_encoding($string, array('UTF-8')));
// UTF-8
var_dump(mb_detect_encoding($string, array('UTF-8', 'Windows-1251')));
// Windows-1251
var_dump(mb_detect_encoding($string, array('UTF-8', 'KOI8-R')));
// KOI8-R
var_dump(mb_detect_encoding($string, array('UTF-8', 'Windows-1251', 'KOI8-R')));
// FALSE
var_dump(mb_detect_encoding($string, array('UTF-8', 'ISO-8859-5')));
// ISO-8859-5
var_dump(mb_detect_encoding($string, array('UTF-8', 'Windows-1251', 'KOI8-R', 'ISO-8859-5')));
// ISO-8859-5

// Теперь $strict = TRUE

var_dump(mb_detect_encoding($string, array('UTF-8'), TRUE));
// FALSE
var_dump(mb_detect_encoding($string, array('UTF-8', 'Windows-1251'), TRUE));
// FALSE
var_dump(mb_detect_encoding($string, array('UTF-8', 'KOI8-R'), TRUE));
// FALSE
var_dump(mb_detect_encoding($string, array('UTF-8', 'Windows-1251', 'KOI8-R'), TRUE));
// FALSE
var_dump(mb_detect_encoding($string, array('UTF-8', 'ISO-8859-5'), TRUE));
// ISO-8859-5
var_dump(mb_detect_encoding($string, array('UTF-8', 'Windows-1251', 'KOI8-R', 'ISO-8859-5'), TRUE));
// ISO-8859-5


Как видим, на выходе — полная каша. Что мы делаем, когда непонятно почему так себя ведет функция? Правильно, гуглим. Нашел замечательный ответ.



Чтобы окончательно развеять все надежды на использование mb_detect_encoding(), надо залезть в исходники расширения mbstring. Итак, закасали рукава, поехали:

// ext/mbstring/mbstring.c:2629
PHP_FUNCTION(mb_detect_encoding)
{
...
// строка 2703
ret = mbfl_identify_encoding_name(&string, elist, size, strict);
...


Ctrl + клик:

// ext/mbstring/libmbfl/mbfl/mbfilter.c:643
const char*
mbfl_identify_encoding_name(mbfl_string *string, enum mbfl_no_encoding *elist, int elistsz, int strict)
{
const mbfl_encoding *encoding;

encoding = mbfl_identify_encoding(string, elist, elistsz, strict);
...


Ctrl + клик:

// ext/mbstring/libmbfl/mbfl/mbfilter.c:557
/*
* identify encoding
*/
const mbfl_encoding *
mbfl_identify_encoding(mbfl_string *string, enum mbfl_no_encoding *elist, int elistsz, int strict)
{
...


Постить полный текст метода не буду, чтобы не засорять статью лишними исходниками. Кому это интересно посмотрят сами. Нас истересует строка под номером 593, где собственно и происходит проверка того, подходит ли символ под кодировку:

// ext/mbstring/libmbfl/mbfl/mbfilter.c:593
(*filter->filter_function)(*p, filter);
if (filter->flag) {
bad++;
}


Вот основные фильтры для однобайтовой кириллицы:



Windows-1251 (оригинальные комментарии сохранены)

// ext/mbstring/libmbfl/filters/mbfilter_cp1251.c:142
/* all of this is so ugly now! */
static int mbfl_filt_ident_cp1251(int c, mbfl_identify_filter *filter)
{
if (c >= 0x80 && c < 0xff)
filter->flag = 0;
else
filter->flag = 1; /* not it */
return c;
}




KOI8-R

// ext/mbstring/libmbfl/filters/mbfilter_koi8r.c:142
static int mbfl_filt_ident_koi8r(int c, mbfl_identify_filter *filter)
{
if (c >= 0x80 && c < 0xff)
filter->flag = 0;
else
filter->flag = 1; /* not it */
return c;
}




ISO-8859-5 (тут вообще все весело)

// ext/mbstring/libmbfl/mbfl/mbfl_ident.c:248
int mbfl_filt_ident_true(int c, mbfl_identify_filter *filter)
{
return c;
}


Как видим, ISO-8859-5 всегда возвращает TRUE (чтобы вернуть FALSE, нужно выставить filter->flag = 1).



Когда посмотрели фильтры, все встало на свои места. CP1251 от KOI8-R не отличить никак. ISO-8859-5 вообще если есть в списке кодировок — будет всегда детектиться как верная.



В общем, fail. Оно и понятно — только по кодам символов нельзя в общем случае узнать кодировку, так как эти коды пересекаются в разных кодировках.




2. Что выдает гугл


А гугл выдает всякие убожества. Даже не буду постить сюда исходники, сами посмотрите, если захотите (уберите пробел после http://, не знаю я как показать текст не ссылкой):



http:// deer.org.ua/2009/10/06/1/

http:// php.su/forum/topic.php?forum=1&topic=1346




3. Поиск по хабру


1) опять коды символов: habrahabr.ru/blogs/php/27378/#comment_710532



2) на мой взгляд, очень интересное решение: habrahabr.ru/blogs/php/27378/#comment_1399654

Минусы и плюсы в комменте по ссылке. Лично я считаю, что только для детекта кодировки это решение избыточно — слишком мощно получается. Определение кодировки в нем — как побочный эффект ).




4. Собственно, мое решение


Идея возникла во время просмотра второй ссылки из прошлого раздела. Идея следующая: берем большой русский текст, замеряем частоты разных букв, по этим частотам детектим кодировку. Забегая вперед, сразу скажу — будут проблемы с большими и маленькими буквами. Поэтому выкладываю примеры частот букв (назовем это — «спектр») как с учетом регистра, так и без (во втором случае к маленькой букве добавлял еще большую с такой же частотой, а большие все удалял). В этих «спектрах» вырезаны все буквы, имеющие частоты меньше 0,001 и пробел. Вот, что у меня получилось после обработки «Войны и Мира»:



Регистрозависимый «спектр»:

array (
'о' => 0.095249209893009,
'е' => 0.06836817536026,
'а' => 0.067481298384992,
'и' => 0.055995027400041,
'н' => 0.052242744063325,
....
'э' => 0.002252892226507,
'Н' => 0.0021318391371162,
'П' => 0.0018574762967903,
'ф' => 0.0015961610948418,
'В' => 0.0014044332975731,
'О' => 0.0013188987793209,
'А' => 0.0012623590130186,
'К' => 0.0011804488387602,
'М' => 0.001061932790165,
)




Регистронезависимый:

array (
'О' => 0.095249209893009,
'о' => 0.095249209893009,
'Е' => 0.06836817536026,
'е' => 0.06836817536026,
'А' => 0.067481298384992,
'а' => 0.067481298384992,
'И' => 0.055995027400041,
'и' => 0.055995027400041,
....
'Ц' => 0.0029893589260344,
'ц' => 0.0029893589260344,
'щ' => 0.0024649163501406,
'Щ' => 0.0024649163501406,
'Э' => 0.002252892226507,
'э' => 0.002252892226507,
'Ф' => 0.0015961610948418,
'ф' => 0.0015961610948418,
)




Спектры в разных кодировках (ключи массива — коды соответствующих символов в соответствующей кодировке):



Windows-1251: case sensitive, case insensitive

KOI8-R: case sensitive, case insensitive

ISO-8859-5: case sensitive, case insensitive



Далее. Берем текст неизвестной кодировки, для каждой проверяемой кодировки находим частоту текущего символа и прибавляем к «рейтингу» этой кодировки. Кодировка с бОльшим рейтингом и есть, скорее всего, кодировка текста.



$encodings = array(
'cp1251' => require 'specter_cp1251.php',
'koi8r' => require 'specter_koi8r.php',
'iso88595' => require 'specter_iso88595.php'
);
$enc_rates = array();
for ($i = 0; $i < len($str); ++$i)
{
foreach ($encodings as $encoding => $char_specter)
{
$enc_rates[$encoding] += $char_specter[ord($str[$i])];
}
}
var_dump($enc_rates);


Даже не пытайтесь выполнить этот код у себя — он не заработает. Можете считать это псевдокодом — я опустил детали, чтобы не загромождать статью. $char_specter — это как раз те массивы, на которые стоят ссылки на pastebin.




Результаты


Строки таблицы — кодировка текста, столбцы — содержимое массива $enc_rates.



1) $str = 'Русский текст';

cp1251 | koi8r | iso88595 |

0.441 | 0.020 | 0.085 | Windows-1251

0.049 | 0.441 | 0.166 | KOI8-R

0.133 | 0.092 | 0.441 | ISO-8859-5



Все отлично. Реальная кодировка имеет уже в 4 раза бОльший рейтинг, чем остальные — это на таком коротком тексте. На более длинных текстах соотношение будет примерно таким же.



2) $str = ' СТРОКА КАПСОМ РУССКИЙ ТЕКСТ';

cp1251 | koi8r | iso88595 |

0.013 | 0.705 | 0.331 | Windows-1251

0.649 | 0.013 | 0.201 | KOI8-R

0.007 | 0.392 | 0.013 | ISO-8859-5





У-упс! Полная каша. А потому что большие буквы в CP1251 обычно соответствуют маленьким в KOI8-R. А маленькие буквы используются в свою очередь намного чаще, чем большие. Вот и определяем строку капсом в CP1251 как KOI8-R.

Пробуем делать без учета регистра («спектры» case insensitive)



1) $str = 'Русский текст';

cp1251 | koi8r | iso88595 |

0.477 | 0.342 | 0.085 | Windows-1251

0.315 | 0.477 | 0.207 | KOI8-R

0.216 | 0.321 | 0.477 | ISO-8859-5





2) $str = ' СТРОКА КАПСОМ РУССКИЙ ТЕКСТ';

cp1251 | koi8r | iso88595 |

1.074 | 0.705 | 0.465 | Windows-1251

0.649 | 1.074 | 0.201 | KOI8-R

0.331 | 0.392 | 1.074 | ISO-8859-5





Как видим, верная кодировка стабильно лидирует и с регистрозависимыми «спектрами» (если строка содержит небольшое количество заглавных букв), и с регистронезависимыми. Во втором случае, с регистронезависимыми, лидирует не так уверенно, конечно, но вполне стабильно даже на маленьких строках. Можно поиграться еще с весами букв — сделать их нелинейными относительно частоты, например.




5. Заключение


В топике не расмотрена работа с UTF-8 — тут никакий принципиальной разницы нету, разве что получение кодов символов и разбиение строки на символы будет несколько длиннее/сложнее.

Эти идеи можно распространить не только на кириллические кодировки, конечно — вопрос только в «спектрах» соответствующих языков/кодировок.



P.S. Если будет очень нужно/интересно — потом выложу второй частью полностью работающую библиотеку на GitHub. Хотя я считаю, что данных в посте вполне достаточно для быстрого написания такой библиотеки и самому под свои нужды — «спектр» для русского языка выложен, его можно без труда перенести на все нужные кодировки.
Original source: habrahabr.ru (comments).

Читать дальше


--------------------

Вернуться в начало страницы
 
+Ответить с цитированием данного сообщения

Ответить в эту темуОткрыть новую тему
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 

Текстовая версия Сейчас: 30.11.2025, 3:37
Яндекс цитирования