Пользователь
0,0
рейтинг
11 ноября 2010 в 19:31

Разработка → Определение кодировки текста в PHP — обзор существующих решений плюс еще один велосипед

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

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. Хотя я считаю, что данных в посте вполне достаточно для быстрого написания такой библиотеки и самому под свои нужды — «спектр» для русского языка выложен, его можно без труда перенести на все нужные кодировки.

UPDATED
В комментариях проскочила замечательная функция, ссылку на которую я опубликовал под графом «убожество». Может быть погорячился со словами, но уж как опубликовал, так опубликовал — редактировать такие вещи не привык. Чтобы не быть голословным, давайте разберемся, работает ли она на 100%, как об этом говорит предполагаемый автор.
1) будут ли ошибки при «нормальной» работе этой функции? Предположим, что контент у нас на 100% валидный.
ответ: да, будут.
2) определит ли она что-нибудь кроме UTF-8 и не-UTF-8?
ответ: нет, не определит.

Вот код:
$str_cp1251 = iconv('UTF-8', 'Windows-1251', 'Русский текст');

var_dump(md5($str_cp1251));
var_dump(md5(iconv('Windows-1251', 'Windows-1251', $str_cp1251)));
var_dump(md5(iconv('KOI8-R', 'KOI8-R', $str_cp1251)));
var_dump(md5(iconv('ISO-8859-5', 'ISO-8859-5', $str_cp1251)));
var_dump(md5(iconv('UTF-8', 'UTF-8', $str_cp1251)));

что на выходе:
m00t@m00t:~/workspace/test$ php detect_encoding.php 
string(32) "96e14d7add82668414ffbc498fcf2a4e"
string(32) "96e14d7add82668414ffbc498fcf2a4e"
string(32) "96e14d7add82668414ffbc498fcf2a4e"
string(32) "96e14d7add82668414ffbc498fcf2a4e"
PHP Notice:  iconv(): Detected an illegal character in input string in /home/m00t/workspace/test/detect_encoding.php on line 36
PHP Stack trace:
PHP   1. {main}() /home/m00t/workspace/test/detect_encoding.php:0
PHP   2. iconv() /home/m00t/workspace/test/detect_encoding.php:36
string(32) "d41d8cd98f00b204e9800998ecf8427e"

Что мы видим? Однобайтовая кириллица после iconv($encoding, $encodigng) не изменится. Так можно отличить только UTF-8 от не-UTF-8. И то — ценой ворнинга.
ИМХО именно вот из-за таких кусков кода и считают PHP «языком для дураков» (с) — как не переминут написать тролли в любом топике про этот язык.
Антон Сердюк @m00t
карма
48,7
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (81)

  • +15
    Всем костылям костыль.
    • +7
      скорее баян — так шифровки дешифровывали еще при царе горохе.
      но, это самый единственный идеологический правильный метод. еще правильнее только атака по словарю, но она намного более ресурсоемка.
      • +1
        Конечно баян. Немного упрощенный, чтобы быть менее ресурсоемким, но в то же время еще работающим. Только вот таких баянов я еще не встречал для детекта кодировок — все пытаются через коды символов узнавать кодировку
  • –3
    Помню когда-то давно была проблема, что какой-то ИЕ отправлял данные на сервер, через ajax, в неверной кодировке. И так пробовал и этак, всё равно неверно, хоть убейся. Так вот, использовал костыль с добавлением параметра только для ИЕ и далее на php проверял. Если есть параметр, менял кодировку данных post. Самый костылистый костыль.
    • 0
      Проблема решалась формированием ответа запроса в виде полноценного xhtml с meta encoding. Сам в свое время столкнулся. ИЕ стал вести себя прилично, а остальные браузеры как работали ранее, так и работали дальше.
    • 0
      Не только ослик, но и сафари. Ибо utf8! Либо вы правильно работаете с кодировками (хедеры, меты и т.д.), либо используете утф. Это не проблема браузера, это с вами проблема.
      • +1
        Вы тут все такие умные капитаны. Написал же, что давно проблема была, а вы так яростно бросились объяснять очевидные вещи.
  • +3
    Я бы рекомендовал использовать биграммы (триграммы) вместе с вероятностью их появления в тексте. Мне в одной из подобных проблем давал более точные результаты.
    • +1
      уверен, что N-граммы дадут более точный результат.
      Похожий подход используется в одной из ссылок для детекта языков, но ИМХО для детекта кодировок это уже несколько избыточно.
      • 0
        очевидно, что N-граммы дадут верный ответ с большей вероятностью, но калькуляция занимает больше времени, в то время как в случае биграмм имхо (естесственно не производил реальных замеров) в несколько раз повышается точность за счет небольшого увеличения времени исполнения.
        • 0
          в свое время тоже необходимо было решить такую задачу.
          По-моему находил где-то решение с ngram на php
          Однако, если уж автор залез внутрь php, не составит труда залезть внутрь firefox и выковырять оттуда модуль определения кодировок
          • 0
            Решение с n-граммами на php есть по ссылке в топике (в разделе «поиск по хабру» вторая ссылка). Действительно на мой взгляд очень хорошее решение проблемы. Просто ИМХО несколько избыточно — для детекта языка в самый раз, а кодировки попроще можно детектить.
            • 0
              Только что увидел Ваш ответ у себя в почте. Ваше решение очень интересное, однако (мое мнение) как Вы считаете правильно ли использовать такие тексты как «Война и Мир» (я бы тоже использовал что-то похожее или тексты lib.ru)?
              Можете быть правильно использовать свежие новости?
  • +1
    Исправьте по тексту «закасали рукава».
    • +4
      на что исправить подскажите, пожалуйста
      • +4
        ну хотя бы на «закатали рукава»
        • +1
          спасибо, поправил.
  • +4
    Модуль chardet — питоновский порт the auto-detection code in Mozilla и как это всё работает A composite approach to language/encoding detection.
    я бы там посмотрел как реализовано
  • 0
    Если сравнивать не частоты появления в тексте символов, а частоты пар, троек или большего количества символов идущих подряд, то определение будет гораздо точнее работать. Пар вполне достаточно. Где-то в сети и библиотеки такие быть должны.
    • 0
      habrahabr.ru/blogs/php/107945/#comment_3411483

      тут об этом написано. Полностью согласен, но считаю полученную точность вполне удовлетворительной ).
    • +1
      Всё уже сказано до нас. Хоть и не начинай писать. :)
      • 0
        Так появился постмодернизм.
  • +1
    Хех, недолго осталось до наивного Байесовского классификатора.
  • +1
    интересная библиотека charset_x_win и метод определения кодировки ivr.webzone.ru/articles/defcod_2/index.htm
    • +1
      Кстати метод в данной библиотеки мне показался более интересным чем в статье.
      Советую сбегать, почитать.
    • 0
      Крутая штука, кстати. Жаль напрямую в юникод не умеет
  • +7
    function get_encoding($str){
        $cp_list = array('utf-8', 'windows-1251');
        foreach ($cp_list as $k=>$codepage){
            if (md5($str) === md5(iconv($codepage, $codepage, $str))){
                return $codepage;
            }
        }
        return null;
    }
    • 0
      сделайте substr() на строке в UTF-8, как иногда делают в тайтлах некоторых сайтов — и все, iconv() сразу споткнется с ворнингом.
      • 0
        Для utf-8 есть mb_substr().
        • 0
          Есть, но проблему уже порезанной строки это не решает.

          PS. Способ хороший, но когда заранее знаешь что на входе валидные данные, иначе нотисы в логах начнут регулярно радовать.
          • 0
            А как так строка станет вдруг порезанной? Если мы её изначально только через mb_ обрабатывали?
            • –1
              Данные, бывает, берут не только из своей системы. Есть такая штука, как интеграция.
        • +1
          К сожалению, не все сайтописатели знают об этой функции.
    • 0
      чем она отличается, от функции с моего блога?
      который здесь идёт как пример «убожества», которое работает 100%
      • 0
        iconv() очень привередливый. Чуть только невалидный контент — и все.
        Да и вообще — одному мне кажется, что делать через ошибки iconv() на неправильных кодировках нельзя?

        Один только я думаю, что приложение на PHP обязано работать при error_reporting(E_ALL) без ошибок, ворнингов и нотисов, а на продакшене просто нужно его выключать на всякий случай?
        • 0
          я наверное что-то пропустил, но к чему этот ответ?
          • 0
            Это обоснование моего личного мнения насчет этой функции. И почему она не работает на 100%
            • 0
              а вы замеряли время работы своей функции по сравнению с иконв, и его ворнингом?
              • 0
                Пожалуйста, прочитайте обновление в посте про эту функцию.
                Кроме того, что она генерирует ворнинги, она еще и не работает, поэтому измерять гипотетическую производительность смысла не вижу.
                • 0
                  Понимаете, была задача, определить текст в УТФ8 или в цп1251, функция работает 100%, без отказов.
                  Теперь скажите реальный пример определения огромного количества кодировок?
                  • +2
                    Не совсем согласен. Функция фактически определяет, строка в utf8 или не в utf8. И работает 100% без отказов если на входе 100% валидная строка. То же самое намного проще сделает preg_match('#.#u'):
                    $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)
                    

                    причем без ворнингов.

                    Задача в посте же ставилась — определять кодировку текста. Однобайтовых кириллических кодировок больше одной, поэтому и функция эта тут немного не в тему, мне кажется. Огромного количества кодировок не надо — достаточно почти всегда и двух однобайтовых кроме utf8 — cp1251 и koi8-r.
                    • +1
                      тогда, задача сократилась к определению утф8, а всё остальное цп1251, да признаю.
                      за новый пример спасибо.
                      зы: на будущее не будь таким высокомерным, эта штука по жизни не очень помогает ;)
                      • 0
                        Спасибо за «зы».
                        Иногда замечаю такое за собой, перечитывая что уже написал. Приму к сведению, постараюсь исправиться.
                    • 0
                      К сожалению, не 100% результат
                    • 0
                      Большое вам спасибо, знай я этот метод раньше — мог бы сэкономить полдня бесплодных усилий:)
      • 0
        Да, вы правы, это оно, ссылку просмотрел, когда читал.
      • 0
        См обновление поста про эту функцию
        • 0
          Спасибо за грамотный разнос
          • 0
            Ловите плюсик за адкеватность )
  • +1
    Я для краулера использовал librcd, либа давала очень хорошие результаты и работало шустро (принцип работы видимо тот же). Написал обертку к php и пользовал.
  • 0
    В качестве другого костыля можно использовать PSpell — проверять по словарю текст в разных кодировках, где ошибок меньше — та кодировка и верна.
    • 0
      Весьма ресурсоемко.
  • +2
    Вот что люди только не придумают, чтобы на юникод не перейти
    • +1
      Вопрос не перехода, а работы с данными в неизвестной кодировке.
    • 0
      Перейдёшь тут
  • 0
    Хм, интересно, а как оно в enca реализовано…
    • 0
      Это намек кстати ;)
      • 0
        Да как раз хотел предложить с помощью Swig сгенерить php-модуль для enca и использовать его. Но вы же знаете PHP-шников :)
        • 0
          ну в крайнем случае через можно ведь и через шел запустить :)
        • 0
          Ага. А потом каждый раз при деплойменте сношаться с админами вражеских серверов, чтобы они установили/разрешили устанвливать свои расширения. Зачем усложнять и так достаточно простую задачу? 10-15 строк на PHP + несколько массивов данных из 15-30 элементов для каждой кодировки — и зачем генерировать php-модули, перегруженные функционалом для определения японских, корейских, китайских кодировок, которые большинству никогда и не понадобятся?
  • +1
    Скорей бы уже наступило светлое юникодное будущее)
    • 0
      в большинстве интернетов и прочих оффлайнов оно уже вполне наступило же )
    • 0
      Это только в России никак не наступит. Так как два байта на букву ппц как много и вообще. Это как с аськой и жж — все уже забыли кроме русских. Контактов видите ли много там полезных.
  • –7
    это только подтверждает мнение о том, что пхп язык для дураков
    раз даже стандартную билиотеку написали с такими вопиющими нарушениями
    это просто смешно товарищи
    • 0
      а есть такое мнение оказывается?

      таких приколов полно в библиотеках любых языков, начиная с библиотек языков, на которых эти языки и библиотеки для них были написаны.

      вы же вряд ли в сорсу когда нибудь лазили?
      • –2
        я лазил в сорсу
        и я знаю софт, либы сорсами которого можно восхищаться
        • +2
          озвучьте их.

          например либы-аналоги mb_detect_encoding.

          мы перенесем их в язык для дураков.

          кстати, mb_string — не «стандартная» библиотека, а так, «еще одна библиотека»… написана азиатами с оглядкой на их С/J/K
  • +2
    По поводу статьи и кодировки выкинуть этот модуль надо mb_detect одинаково не работает
    как для кирилических кодировок также и не работает для азиатских…

    Было дело Китайскую и корейскую кодировку распозновал всегда как японскую =)

    www.mozilla.org/projects/intl/UniversalCharsetDetection.html Это Теория на тему композитного распознования языков, по сути что автор сделал в статье подобный метод, частоты встречания символов.

  • +2
    Частотное распределение имеет ошибку зависящую от количества символов и до 50 и даже до 100 она достаточно большая. Если надо распознавать фразу или даже несколько слов, то надо делать распределение или веса пары символов.
    • +1
      Согласен. Как по мне, описанный в статье метод нельзя использовать для произвольных текстов. Он подходит только для определения относительно длинных текстов, для текстов из 3 слов он, выражаясь словами автора, убожество.
      • 0
        Прежде чем критиковать, не мешало бы внимательно прочитать статью и посмотреть примеры.
        Мои замеры в примерах на текстах из трех слов приведены в статье — все работает, как ни странно, даже на таких коротких текстах.
        • –1
          сравните производительность вашего убожества и других
  • +2
    По большому счёту когда приходят данные в неизвестной кодировке важно определить — это UTF или нет, в противном случае принимаем волевое решение что это codepage 1251. На извращенцев, по недоразумению до сих пор использующих КОИ-как, EBCDIC или Mazowia можно забить имхо. А определить UTF не легко а очень легко: if (preg_match('//u', $string))…
    • 0
      Насчет определения UTF-8 — согласен (http://habrahabr.ru/blogs/php/107945/#comment_3413195)
      Насчет забить на все не-cp1251 кодировки — к сожалению, не могу себе такого позволить. Да и не только я, мне кажется.
  • 0
    В одном из проектов тоже столкнулся с проблемой определения кодировки приходящего текста.
    В багтрекере PHP даже баг заведён по этому поводу: bugs.php.net/bug.php?id=38138

    А для проверки кодировки текста я бы рекоммендовал использовать функцию mb_check_encoding(). Она работает достаточно адекватно. По крайней мере мои проверки (UTF-8, Windows-1251, KOI8-R, ISO-8859-5) не выявили проблем. В принципе на основе этой функции также можно написать небольшой велосипедик по определению самых распространённых кодировок.

    Внутрь исходников расширения mbstring не заглядывал, но теперь стало интересно как работает вышеназванная mb_check_encoding. Вполне возможно там могут использоваться те же функции что и в md_detect_encoding, тогда на надёжность работы уже полагаться не придётся. Надо бы проверить.
  • 0
    Статистика, сэр))
    Спасибо что проделали такой обьем работы!!! Тема — более чем актуальна!
  • 0
    Вы плохо искали ;)

    forum.dklab.ru/viewtopic.php?t=37830

    принцип работы — тот же:

    function detect_encoding($string, $pattern_size = 50)
    {
        $list = array('cp1251', 'utf-8', 'ascii', '855', 'KOI8R', 'ISO-IR-111', 'CP866', 'KOI8U');
        $c = strlen($string);
        if ($c > $pattern_size)
        {
            $string = substr($string, floor(($c - $pattern_size) /2), $pattern_size);
            $c = $pattern_size;
        }
    
        $reg1 = '/(\xE0|\xE5|\xE8|\xEE|\xF3|\xFB|\xFD|\xFE|\xFF)/i';
        $reg2 = '/(\xE1|\xE2|\xE3|\xE4|\xE6|\xE7|\xE9|\xEA|\xEB|\xEC|\xED|\xEF|\xF0|\xF1|\xF2|\xF4|\xF5|\xF6|\xF7|\xF8|\xF9|\xFA|\xFC)/i';
    
        $mk = 10000;
        $enc = 'ascii';
        foreach ($list as $item)
        {
            $sample1 = @iconv($item, 'cp1251', $string);
            $gl = @preg_match_all($reg1, $sample1, $arr);
            $sl = @preg_match_all($reg2, $sample1, $arr);
            if (!$gl || !$sl) continue;
            $k = abs(3 - ($sl / $gl));
            $k += $c - $gl - $sl;
            if ($k < $mk)
            {
                $enc = $item;
                $mk = $k;
            }
        }
        return $enc;
    
    • 0
      Интересное решение, спасибо. Пугает немного только непонятный алгоритм
      • 0
                $k = abs(3 - ($sl / $gl));
                $k += $c - $gl - $sl;
                if ($k < $mk)
                {
                    $enc = $item;
                    $mk = $k;
                }
        
    • 0
      Раньше отправилось, сорри

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.