Как быстро определить кодировку текстовой строки? Определение кодировки текста в PHP и Python Php как узнать сопоставление кодировки.

Возникла проблема: как быстро определить кодировку текстовой строки относительно UTF-8 Всё чаще приходится работать со строками в кодировке UNICODE.

Ниже представлена функция проверки, нужно ли преобразование кодировки UNICODE (UTF-8) в кодировку WINDOWS (win-1251)

Функция даёт довольно точный ответ, хотя построена не на по-кодовом преобразовании символов.

function detect_my_utf($s){ $s=urlencode($s); // в некоторых случаях - лишняя операция (закоментируйте) $res="0"; $j=strlen($s); $s2=strtoupper($s); $s2=str_replace("%D0","",$s2); $s2=str_replace("%D1","",$s2); $k=strlen($s2); $m=1; if ($k>0){ $m=$j/$k; if (($m>1.2)&&($m

Кратко - описание функции detect_my_utf() :

  • преобразуем (строку в специальный формат)
  • вычисляем длину входящей строки
  • приводим все буквы строки в заглавные
  • убираем специфические коды %D0 и %D1
  • вычисляем длину новой строки
  • получаем соотношение тарой строки к новой

Если это соотношение 1 или близко к нему, то есть подозрение, что входящая строка не кодировалась в UNICODE. Если это соотношение находится в диапазоне от 1,2 до 2,2 - то можно смело перекодировать строку в WINDOWS кодировку win-1251.

На выходе функции имеем 0 или 1, соответственно, не UNICODE или UNICODE.

Примеры выполнения функции:

Входящая строка: РїР?С?С?Р?Р?Рє С?Р?Р·Р?Р°Р?РёС? Р°Р?РёР?Р°С+РёРё Р? imageready Преобразованная строка: %D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BE%D0%BA %D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D1%8F %D0%B0%D0%BD%D0%B8%D0%BC%D0%B0%D1%86%D0%B8%D0%B8%20%D0%B2 imageready Результат работы функции: 1 Закодированная фраза: порядок создания анимации в imageready

Входящая строка: Р?С?Р?Р?Р?С

Входящая строка: РїС?Р?Р?С?Р°Р?Р?Р° С+С"РчР?РёС? РїР?С+Р°С?апаР?Р?С

Входящая строка: пособие по рисованию Преобразованная строка: %EF%EE%F1%EE%E1%E8%E5 %EF%EE %F0%E8%F1%EE%E2%E0%ED%E8%FE Результат работы функции: 0 Закодированная фраза: пособие по рисованию Данный алгоритм хорошо справляется с разнообразными входящими строками в составе сервиса статистики переходов с поисковых машин .

Интересные материалы на сайте:

  • Статья о поисковой интересности сайта. Возможно, часть материалов уже устарела за 10 лет, но на некоторые моменты стоит обратить внимание.

  • Свой взгляд на проблему обмена гипер ссылками между сайтом донором и сайтами получателями.

  • Еще один лайф-хак. Обыгрываем нечестных игроков в игру "Балда". Большая база слов, которая с легкостью может быть расширена.

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

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->

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,)

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

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

$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)]; } } var_dump($enc_rates);
Даже не пытайтесь выполнить этот код у себя - он не заработает. Можете считать это псевдокодом - я опустил детали, чтобы не загромождать статью. $char_specter - это как раз те массивы, на которые стоят ссылки на pastebin.

Результаты
Строки таблицы - кодировка текста, столбцы - содержимое массива $enc_rates.

1) $str = "Русский текст";
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 раза бОльший рейтинг, чем остальные - это на таком коротком тексте. На более длинных текстах соотношение будет примерно таким же.


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

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

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->

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) опять коды символов:

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

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,)

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

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

$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)]; } } var_dump($enc_rates);
Даже не пытайтесь выполнить этот код у себя - он не заработает. Можете считать это псевдокодом - я опустил детали, чтобы не загромождать статью. $char_specter - это как раз те массивы, на которые стоят ссылки на pastebin.

Результаты
Строки таблицы - кодировка текста, столбцы - содержимое массива $enc_rates.

1) $str = "Русский текст";
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 раза бОльший рейтинг, чем остальные - это на таком коротком тексте. На более длинных текстах соотношение будет примерно таким же.


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

Я читаю много текстов из разных RSS-каналов и вставляю их в свою базу данных.

Конечно, существует несколько различных кодировок символов, используемых в каналах, например. UTF-8 и ISO-8859-1.

К сожалению, иногда возникают проблемы с кодировками текстов. Пример:

1) "ß" в "Fußball" должен выглядеть так в моей базе данных: "Ÿ". Если это "Ÿ", оно отображается правильно.

2) Иногда "ß" в "Fußball" выглядит так в моей базе данных: "ß". Тогда это отображается неправильно, конечно.

3) В других случаях "ß" сохраняется как "ß" - поэтому без каких-либо изменений. Затем он также отображается неправильно.

Что я могу сделать, чтобы избежать случаев 2 и 3?

Как я могу сделать все одинаковое кодирование, желательно UTF-8? Когда я должен использовать utf8_encode(), когда я должен использовать utf8_decode() (он ясно, что такое эффект, но когда я должен использовать функции?), И когда я должен ничего делать с вводом?

Можете ли вы мне помочь и рассказать мне, как сделать все одинаковое кодирование? Возможно, с функцией mb-detect-encoding()? Могу ли я написать функцию для этого? Поэтому мои проблемы: 1) Как узнать, какая кодировка используется в тексте 2) Как преобразовать его в UTF-8 - независимо от старой кодировки

EDIT: Будет ли такая функция работать?

Function correct_encoding($text) { $current_encoding = mb_detect_encoding($text, "auto"); $text = iconv($current_encoding, "UTF-8", $text); return $text; }

Я тестировал его, но он не работает. Что с ним не так?

24 ответа

Если вы примените utf8_encode() к уже существующей строке UTF8, она вернет искаженный вывод UTF8.

Я создал функцию, которая устраняет все эти проблемы. Он называется Encoding:: toUTF8().

Вам не нужно знать, что такое кодировка ваших строк. Это может быть Latin1 (iso 8859-1), Windows-1252 или UTF8, или строка может содержать их. Кодирование:: toUTF8() преобразует все в UTF8.

Я сделал это, потому что служба давала мне поток данных, все испорченные, смешивая UTF8 и Latin1 в одной строке.

Использование:

Require_once("Encoding.php"); use \ForceUTF8\Encoding; // It namespaced now. $utf8_string = Encoding::toUTF8($utf8_or_latin1_or_mixed_string); $latin1_string = Encoding::toLatin1($utf8_or_latin1_or_mixed_string);

Я включил еще одну функцию, Encoding:: fixUFT8(), которая исправит каждую строку UTF8, которая выглядит искаженной.

Использование:

Require_once("Encoding.php"); use \ForceUTF8\Encoding; // It namespaced now. $utf8_string = Encoding::fixUTF8($garbled_utf8_string);

Echo Encoding::fixUTF8("Fédération Camerounaise de Football"); echo Encoding::fixUTF8("Fédération Camerounaise de Football"); echo Encoding::fixUTF8("FÃÂédÃÂération Camerounaise de Football"); echo Encoding::fixUTF8("Fédération Camerounaise de Football");

Fédération Camerounaise de Football Fédération Camerounaise de Football Fédération Camerounaise de Football Fédération Camerounaise de Football

Обновление: я преобразовал функцию (forceUTF8) в семейство статических функций в классе Encoding. Новая функция - Encoding:: toUTF8().

Сначала вам нужно определить, какая кодировка была использована. Поскольку вы разбираете RSS-каналы (возможно, через HTTP), вы должны прочитать кодировку из параметра charset поля Content-Type HTTP-заголовка . Если он отсутствует, прочитайте кодировку из атрибута encoding инструкции обработки. Если это тоже не хватает, использовать UTF-8, как определено в спецификации .

Изменить Вот что я, вероятно, сделаю:

Обнаружение кодировки затруднено.

mb_detect_encoding работает, гадая, исходя из нескольких кандидатов, которые вы передаете. В некоторых кодировках определенные байтовые последовательности являются недействительными, поэтому он может различать различные кандидаты. К сожалению, существует множество кодировок, в которых одни и те же байты действительны (но разные). В этих случаях невозможно определить кодирование; Вы можете реализовать свою собственную логику, чтобы делать догадки в этих случаях. Например, данные, поступающие с японского сайта, скорее всего, будут иметь японскую кодировку.

Пока вы имеете дело только с западноевропейскими языками, рассмотрим три основных кодировки: utf-8 , iso-8859-1 и cp-1252 . Поскольку они являются значениями по умолчанию для многих платформ, они также, скорее всего, ошибочно сообщаются. Например. если люди используют разные кодировки, они, вероятно, будут откровенны в этом, потому что иначе их программное обеспечение будет ломаться очень часто. Поэтому хорошей стратегией является доверие к провайдеру, если только кодировка не объявлена ​​как одна из этих трех. Вы все равно должны удвоить, что это действительно действительно, используя mb_check_encoding (обратите внимание, что действительный - это не то же самое, что и есть - тот же ввод может быть действителен для многих кодировок). Если это один из них, вы можете использовать mb_detect_encoding , чтобы различать их. К счастью, это довольно детерминировано; Вам просто нужно использовать правильную последовательность обнаружения, которая UTF-8,ISO-8859-1,WINDOWS-1252 .

После того, как вы обнаружили кодировку, вам необходимо преобразовать ее во внутреннее представление (utf-8 - единственный разумный выбор). Функция utf8_encode преобразует iso-8859-1 в utf-8 , поэтому она может использоваться только для этого конкретного типа ввода. Для других кодировок используйте mb_convert_encoding .

В этом чит-лист перечислены некоторые общие оговорки, связанные с обработкой UTF-8 в PHP: http://developer.loftdigital.com/blog/php-utf-8-cheatsheet

Эта функция, обнаруживающая многобайтовые символы в строке, также может оказаться полезной ():

Function detectUTF8($string) { return preg_match("%(?: [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte |\xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs |[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte |\xED[\x80-\x9F][\x80-\xBF] # excluding surrogates |\xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 |[\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 |\xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16)+%xs", $string); }

Немного хедз-ап, вы сказали, что "ß" должен отображаться как "Ÿ" в вашей базе данных.

Вероятно, это связано с тем, что вы используете базу данных с кодировкой символов latin1 или, возможно, неправильно настроено соединение php-mysql, php полагает, что ваш mysql настроен на использование utf-8, поэтому он отправляет данные как utf8, но ваш mysql belives php отправляет данные, закодированные как iso-8859-1, поэтому он может снова попытаться кодировать ваши отправленные данные как utf-8, вызывая такие проблемы.

Взгляните на это, может вам помочь: http://php.net/manual/en/function.mysql-set-charset.php

Вам нужно проверить кодировку на входе, так как ответы могут быть закодированы с разными кодировками.
Я заставляю весь контент отправляться в UTF-8, выполняя обнаружение и перевод, используя следующую функцию:

Function fixRequestCharset() { $ref = array(&$_GET, &$_POST, &$_REQUEST); foreach ($ref as &$var) { foreach ($var as $key => $val) { $encoding = mb_detect_encoding($var[ $key ], mb_detect_order(), true); if (!$encoding) continue; if (strcasecmp($encoding, "UTF-8") != 0) { $encoding = iconv($encoding, "UTF-8", $var[ $key ]); if ($encoding === false) continue; $var[ $key ] = $encoding; } } } }

Эта процедура превратит все переменные PHP, которые поступают с удаленного хоста в UTF-8.
Или игнорировать значение, если кодирование невозможно обнаружить или преобразовать.
Вы можете настроить его в соответствии с вашими потребностями.
Просто вызовите его перед использованием переменных.

Ваша кодировка выглядит так, как будто вы закодированы в UTF-8 дважды ; то есть с некоторой другой кодировки, в UTF-8 и снова в UTF-8. Как будто у вас был iso-8859-1, преобразованный из iso-8859-1 в utf-8 и обработанный новой строкой как iso-8859-1 для другого преобразования в UTF-8.

Вот некоторые псевдокоды того, что вы сделали:

$inputstring = getFromUser(); $utf8string = iconv($current_encoding, "utf-8", $inputstring); $flawedstring = iconv($current_encoding, "utf-8", $utf8string);

Вы должны попробовать:

  • обнаруживать кодировку с помощью mb_detect_encoding() или как вам нравится использовать
  • если UTF-8, преобразовать в iso-8859-1 и повторить шаг 1
  • наконец, конвертировать обратно в UTF-8

Предполагается, что в "среднем" преобразовании вы использовали iso-8859-1. Если вы использовали windows-1252, то конвертируйте в windows-1252 (latin1). Исходная кодировка источника не важна; тот, который вы использовали в ошибочном, второе преобразование.

Это мое предположение о том, что произошло; вы могли бы сделать еще немного, чтобы получить четыре байта вместо одного расширенного байта ASCII.

Немецкий язык также использует iso-8859-2 и windows-1250 (latin2).

Разработка кодировки символов RSS-каналов выглядит сложной . Даже обычные веб-страницы часто опускают или ложатся о своей кодировке.

Итак, вы можете попытаться использовать правильный способ обнаружения кодировки, а затем вернуться к некоторой форме автоматического обнаружения (угадывания).

Я знаю, что это старый вопрос, но я считаю, что полезный ответ никогда не болит. У меня возникли проблемы с моей кодировкой между настольными приложениями, SQLite и GET/POST-переменными. Некоторые из них будут в UTF-8, некоторые из них будут в ASCII, и в основном все будет запутано, когда будут задействованы иностранные персонажи.

Вот мое решение. Он сглаживает ваш GET/POST/REQUEST (я пропустил файлы cookie, но вы можете добавить их, если это необходимо) при каждой загрузке страницы перед обработкой. Он хорошо работает в заголовке. PHP будет выдавать предупреждения, если он не может автоматически определить исходную кодировку, поэтому эти предупреждения подавляются с помощью @.

//Convert everything in our vars to UTF-8 for playing nice with the database... //Use some auto detection here to help us not double-encode... //Suppress possible warnings with @ for when encoding cannot be detected try { $process = array(&$_GET, &$_POST, &$_REQUEST); while (list($key, $val) = each($process)) { foreach ($val as $k => $v) { unset($process[$key][$k]); if (is_array($v)) { $process[$key][@mb_convert_encoding($k,"UTF-8","auto")] = $v; $process = &$process[$key][@mb_convert_encoding($k,"UTF-8","auto")]; } else { $process[$key][@mb_convert_encoding($k,"UTF-8","auto")] = @mb_convert_encoding($v,"UTF-8","auto"); } } } unset($process); } catch(Exception $ex){}

Я проверял решения для кодирования с AGES, и эта страница, вероятно, является завершением лет поиска! Я проверил некоторые из предложений, которые вы упомянули, и здесь мои заметки:

Это моя тестовая строка:

это строка "wròng wrìtten", которую я не использовал для специальной настройки chàrs, чтобы увидеть thèm, convertèd by fùnctìon!! и что это!

Шрифт моей страницы - UTF-8

Если я делаю INSERT именно так, в моей БД у меня есть некоторые символы, которые, вероятно, приходят с Марса... поэтому мне нужно преобразовать их в "здравомыслящий" UTF-8. Я пробовал utf8_encode() , но все еще инопланетные символы вторгались в мою базу данных...

Итак, я попытался использовать функцию forceUTF8 , размещенную по номеру 8, но в БД сохраненная строка выглядит следующим образом:

это "wròng wrìtten" string bùt я nèed to pù "sà ²me" special chà rs, чтобы увидеть thèm, convertèd by fùnctìon!! и что это!

Итак, собрав еще одну информацию на этой странице и объединив ее с другой информацией на других страницах, я решил проблему с этим решением:

$finallyIDidIt = mb_convert_encoding($string, mysql_client_encoding($resourceID), mb_detect_encoding($string));

Теперь в моей базе данных у меня есть строка с правильной кодировкой.

Примечание: Только отметить, чтобы позаботиться о функции mysql_client_encoding ! Вы должны быть подключены к БД, потому что эта функция требует идентификатора ресурса в качестве параметра.

Но хорошо, я просто делаю это перекодирование перед моим INSERT, поэтому для меня это не проблема.

Я надеюсь, что это поможет кому-то, как эта страница, помогла мне!

Спасибо всем!

Интересная вещь о mb_detect_encoding и mb_convert_encoding заключается в том, что порядок кодировок, которые вы предлагаете, имеет значение:

// $input is actually UTF-8 mb_detect_encoding($input, "UTF-8", "ISO-8859-9, UTF-8"); // ISO-8859-9 (WRONG!) mb_detect_encoding($input, "UTF-8", "UTF-8, ISO-8859-9"); // UTF-8 (OK)

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

Echo mb_detect_encoding($str, "auto");

Echo mb_detect_encoding($str, "UTF-8, ASCII, ISO-8859-1");

Я действительно не знаю, каковы результаты, но я предлагаю вам просто взять некоторые из ваших каналов с разными кодировками и попробовать, если mb_detect_encoding работает или нет.

Обновление
auto сокращен для "ASCII, JIS, UTF-8, EUC-JP, SJIS". он возвращает обнаруженную кодировку, которую вы можете использовать для преобразования строки в utf-8 с iconv .

Я не тестировал его, поэтому никаких гарантий. и, возможно, есть более простой способ.

Эта версия предназначена для немецкого языка, но вы можете модифицировать $CHARSETS и $TESTCHARS

Class CharsetDetector { private static $CHARSETS = array("ISO_8859-1", "ISO_8859-15", "CP850"); private static $TESTCHARS = array("€", "ä", "Ä", "ö", "Ö", "ü", "Ü", "ß"); public static function convert($string) { return self::__iconv($string, self::getCharset($string)); } public static function getCharset($string) { $normalized = self::__normalize($string); if(!strlen($normalized))return "UTF-8"; $best = "UTF-8"; $charcountbest = 0; foreach (self::$CHARSETS as $charset) { $str = self::__iconv($normalized, $charset); $charcount = 0; $stop = mb_strlen($str, "UTF-8"); for($idx = 0; $idx < $stop; $idx++) { $char = mb_substr($str, $idx, 1, "UTF-8"); foreach (self::$TESTCHARS as $testchar) { if($char == $testchar) { $charcount++; break; } } } if($charcount>$charcountbest) { $charcountbest=$charcount; $best=$charset; } //echo $text."
"; } return $best; } private static function __normalize($str) { $len = strlen($str); $ret = ""; for($i = 0; $i < $len; $i++){ $c = ord($str[$i]); if ($c > 128) { if (($c > 247)) $ret .=$str[$i]; elseif ($c > 239) $bytes = 4; elseif ($c > 223) $bytes = 3; elseif ($c > 191) $bytes = 2; else $ret .=$str[$i]; if (($i + $bytes) > $len) $ret .=$str[$i]; $ret2=$str[$i]; while ($bytes > 1) { $i++; $b = ord($str[$i]); if ($b < 128 || $b > 191) {$ret .=$ret2; $ret2=""; $i+=$bytes-1;$bytes=1; break;} else $ret2.=$str[$i]; $bytes--; } } } return $ret; } private static function __iconv($string, $charset) { return iconv ($charset, "UTF-8" , $string); } }

После сортировки ваших php-скриптов, не забудьте сказать mysql, какую кодировку вы передаете, и хотели бы получить ее.

Пример: установка набора символов utf8

Передача данных utf8 в таблицу latin1 в сеансе ввода-вывода latin1 дает эти неприятные птичьи птицы. Я вижу это каждый день в магазинах торговли. Назад и четвертое может показаться правильным. Но phpmyadmin покажет правду. Говоря mysql, какая кодировка, которую вы передаете, будет обрабатывать данные mysql для вас.

Как восстановить существующие скремблированные данные mysql - это еще один вопрос для обсуждения.:)

Идея - Лотерея

Саму идею получения кодировки придумал не я, но и автора, к сожалению, сообщить сейчас уже не могу, так как это было порядка 4 лет назад и откуда я взял эту информацию - уже давно забылось. Автор предложил вариант определения и показал пример для 1-2 кодировок на языке Python. Простота его решения не оставила меня в стороне, и я развил её до желаемого результата.
Суть идеи заключается в самих кодовых таблицах кодировок. Как известно, любая кодировка содержит свою кодовую таблицу и для каждого символа кодировки присвоено определенное значение. Таблицы кодировок я здесь показывать не буду, сейчас их найти в интернете достаточно просто.
Принцип реализации следующий:
  1. Создается переменная-массив для хранения результата "анализа" проверенного текста. Каждый элемент массива будет содержать результат для конкретной кодировки.
  2. Полученный на вход функции текст перебирается по символьно.
  3. От каждого символа берется ординал (значение этого символа) и сравнивается с диапазоном кодировки.
  4. Если значение выпадает на прописной (заглавный) символ, элементу массива, который хранит результат этой кодировки, прибавляется значение 1.
  5. Если значение выпадает на строчный (маленький) символ, элементу массива, который хранит результат этой кодировки, прибавляется значение 3.
  6. Та кодировка, точнее, тот элемент массива, который хранит результат о своей кодировке, который набрал больше всего баллов - вероятней всего и является исходной кодировкой.
Такой алгоритм справедлив для однобайтовых кодировок, таких как KOI-8, CP1251 (windows-1251) и других. Однако, для двухбайтовых кодировок (в моем случае UTF-8), такой подход выдаст ошибочный результат. Для начала я попытался решить этот вопрос путем прибавления для прописных символов - 5, для строчных - 7. Результат стал лучше, однако все равно ошибки распознавания присутствовали. После недолгих экспериментов, я вывел, что для верного определения UTF, при прописных символах должно прибавляться к результату 10, для строчных 14, то есть в 2 раза больше начального моего предположения. Тем не менее, для лучшего визуального понимания кода я для символов UTF оставил 5 и 7 соответственно и уже во время проверки эти значения увеличивал на 2 и прибавлял к результату.
Вот в принципе и весь алгоритм. И без всяких лишних заморочек.
Больше всего времени на реализацию этой функции у меня убилось конечно же на поиск кодовых таблиц и правильной расстановки диапазонов. Мало того, что на тот момент, когда я первый раз писал эту функцию, найти актуальную кодовую таблицу было достаточно трудно, так ещё диапазоны символов в них скачут как попало. Тем не менее, я тогда остановился на самых актуальных (и по сей день) кодировках: UTF-8, CP1251, KOI8-R, IBM866, ISO-8859-5 и MAC. Если вам недостаточно этих кодировок, вы можете на основе данного алгоритма дополнить код.

От слов к практике

Собственно, весь код функции на Python выглядит следующим образом:

Encodings = { "UTF-8": "utf-8", "CP1251": "windows-1251", "KOI8-R": "koi8-r", "IBM866": "ibm866", "ISO-8859-5": "iso-8859-5", "MAC": "mac", } """ Определение кодировки текста """ def get_codepage(str = None): uppercase = 1 lowercase = 3 utfupper = 5 utflower = 7 codepages = {} for enc in encodings.keys(): codepages = 0 if str is not None and len(str) > 0: last_simb = 0 for simb in str: simb_ord = ord(simb) """non-russian characters""" if simb_ord < 128 or simb_ord > 256: continue """UTF-8""" if last_simb == 208 and (143 < simb_ord < 176 or simb_ord == 129): codepages["UTF-8"] += (utfupper * 2) if (last_simb == 208 and (simb_ord == 145 or 175 < simb_ord < 192)) \ or (last_simb == 209 and (127 < simb_ord < 144)): codepages["UTF-8"] += (utflower * 2) """CP1251""" if 223 < simb_ord < 256 or simb_ord == 184: codepages["CP1251"] += lowercase if 191 < simb_ord < 224 or simb_ord == 168: codepages["CP1251"] += uppercase """KOI8-R""" if 191 < simb_ord < 224 or simb_ord == 163: codepages["KOI8-R"] += lowercase if 222 < simb_ord < 256 or simb_ord == 179: codepages["KOI8-R"] += uppercase """IBM866""" if 159 < simb_ord < 176 or 223 < simb_ord < 241: codepages["IBM866"] += lowercase if 127 < simb_ord < 160 or simb_ord == 241: codepages["IBM866"] += uppercase """ISO-8859-5""" if 207 < simb_ord < 240 or simb_ord == 161: codepages["ISO-8859-5"] += lowercase if 175 < simb_ord < 208 or simb_ord == 241: codepages["ISO-8859-5"] += uppercase """MAC""" if 221 < simb_ord < 255: codepages["MAC"] += lowercase if 127 < simb_ord < 160: codepages["MAC"] += uppercase last_simb = simb_ord idx = "" max = 0 for item in codepages: if codepages > max: max = codepages idx = item return idx
Пример вызова функции

Print encodings

А что же на счет PHP

Переписать уже готовую функцию из Python в PHP не составило никакого труда. По своему виду он практически ничем не отличается от его прородителя на Python:

/** * Определение кодировки текста * @param String $text Текст * @return String Кодировка текста */ function get_codepage($text = "") { if (!empty($text)) { $utflower = 7; $utfupper = 5; $lowercase = 3; $uppercase = 1; $last_simb = 0; $charsets = array("UTF-8" => 0, "CP1251" => 0, "KOI8-R" => 0, "IBM866" => 0, "ISO-8859-5" => 0, "MAC" => 0,); for ($a = 0; $a < strlen($text); $a++) { $char = ord($text[$a]); // non-russian characters if ($char<128 || $char>256) continue; // UTF-8 if (($last_simb==208) && (($char>143 && $char<176) || $char==129)) $charsets["UTF-8"] += ($utfupper * 2); if ((($last_simb==208) && (($char>175 && $char<192) || $char==145)) || ($last_simb==209 && $char>127 && $char<144)) $charsets["UTF-8"] += ($utflower * 2); // CP1251 if (($char>223 && $char<256) || $char==184) $charsets["CP1251"] += $lowercase; if (($char>191 && $char<224) || $char==168) $charsets["CP1251"] += $uppercase; // KOI8-R if (($char>191 && $char<224) || $char==163) $charsets["KOI8-R"] += $lowercase; if (($char>222 && $char<256) || $char==179) $charsets["KOI8-R"] += $uppercase; // IBM866 if (($char>159 && $char<176) || ($char>223 && $char<241)) $charsets["IBM866"] += $lowercase; if (($char>127 && $char<160) || $char==241) $charsets["IBM866"] += $uppercase; // ISO-8859-5 if (($char>207 && $char<240) || $char==161) $charsets["ISO-8859-5"] += $lowercase; if (($char>175 && $char<208) || $char==241) $charsets["ISO-8859-5"] += $uppercase; // MAC if ($char>221 && $char<255) $charsets["MAC"] += $lowercase; if ($char>127 && $char<160) $charsets["MAC"] += $uppercase; $last_simb = $char; } arsort($charsets); return key($charsets); } }
Пример вызова функции

Echo get_codepage(file_get_contents("test.txt"));

ЛикБез, или Не мешайте машине работать

Не стоит пытаться устраивать crash-test для этой функции. Из алгоритма понятно, что чем меньше текста ей придет на вход, тем больше вероятности, что функция распознает кодировку не верно. С другой стороны, скармливать тома Льва Толстого тоже не имеет смысла: данный метод прекрасно справляется с небольшим предложением в 100-200 символов. И хоть я в примерах вызова на вход отправлял все содержимое некоего файла "test.txt", в котором предполагалось, что находится текст, кодировку которого нужно определить, на вход функции можно (и нужно) передавать небольшой участок текста.
Извращения с перемешанными прописными и строчными буквами вообще считаю не уместными в данном случае, так как этот метод писался для обычной заурядной задачи с приближенно грамотным русским языком. А такие эксперименты мне чаще всего напоминают анекдот:

Русский древообрабатывающий завод приобрел японский агрегат. Собрались русские рабочие вокруг него и давай разбираться, как он работает. Один взял доску, сунул в нее. Агрегат: дзззззззззззззззынь... На выходе готовая табуретка. Мужики: Ни чего себе!!! Агрегат на дисплее: ну а как вы думали? Другой взял не обтесанное бревно, вставил в агрегат. Агрегат: дзззззззззззззззынь... На выходе готовый резной сервант. Мужики: Ни чего себе!!! Агрегат на дисплее: ну а как вы думали? Третий мужик не выдержал, откуда то притянул рельсу и всунул в агрегат. Агрегат: дрррррр-тых-тых-тых... Задымился и на дисплее надпись: Ни чего себе!!! Мужики: ну а как ты думала!!!
Так что для подобных извращенных тестов вам скорее всего понадобится извращенный алгоритм, каким данная функция не является. А из практики скажу, что за время использования в течении 4 лет, данный метод меня ни разу не подвел и всегда давал верный результат.
Надеюсь моя статья станет кому то полезной.
Спасибо за внимание.

При использовании всего или части содержимого, не забывайте указывать ссылку на источник, то есть на мой блог.