Портал Belkin-labs»PHP классы»Статья
welcome!

Занимательные кодировки, или опыты с текстом UTF-8

Сегодня задался целью сделать простейшую задачу. Конвертировать в одной папке все файлы из кодировки Cp1251 в кодировку utf-8. Реализовано это было на PHP и очень быстро, но мне тут же захотелось свою приблуду улучшить и предотвратить возможное повторное кодирование utf-8. И вот это оказалось довольно нетривиальной и очень интересной задачей.

Во-первых, я уже занимался как-то конвертацией текста из Cp1251 в utf-8. Был сделан специальный класс. который, кстати, хорошо себя зарекомендовал и которым я иногда даже пользуюсь. Я использовал его и в этой своей задаче, поскольку кроме русского и английского текстов у меня в файлах и нет ничего.

Мне хотелось сделать попроще. Я подумал наставить себе всяких ограничений и свести задачу к чему-то очень простому и более или менее работоспособному.

Я стал разбираться. Посмотрев, что предлагают другие программисты, мне стало как-то грустно, поскольку одни выдают довольно сложные регулярки, пестрящие символьными классами \xhh, другие ничего не предлагают, но зато утверждают, что достоверно угадать кодировку невозможно. А я это и так знаю. BOM тоже не совсем то, что мне нужно. Опять надо разбираться в новой инфе, а времени нет. Желания тоже, кстати.

Но я уже поставил ограничение. Только русский и английский текст. Пришлось вспоминать, что такое utf-8 в приложении к русскому тексту (по материалам своего же класса). Пока разбирался в своих же кодах не один раз удивился, как я умудрился такой красивый и непонятный код написать...

Короче говоря, очень скоро выяснилось, что за очень малым исключением все русские символы преобразуются в utf-8 двухбайтовый символ. Причем первым байтом стоит либо Русская буква "Р" или русская же буква "С". Еще пару раз встречаются символы "ё" и "Ё" и один раз встречается буква "в". Начнем же наши опыты (ради них и писалась статья. Именно опыты могут быть полезны.


    include_once('class.CYR_UTF8.php');
    $utf8 = new CYR_UTF8();

    $text = 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯабвгдеёжзиклмнопрстуфхсчшщьыъэюя .№.';
    $converted1 = $utf8->to_utf8_r($text);
    echo('

Исходный текст:
'.$text.'

'
. '

utf8 (первый раз):
'.$converted1.'

'
); exit; // Исходный текст: // АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯабвгдеёжзиклмнопрстуфхсчшщьыъэюя .№. // // utf8 (первый раз): // АБВГДЕЁЖЗР�ЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯабвгдеёжзиклмнопрстуфхсчшщьыъэюя .в„–. ?>

И в финале игры со своим классом я попробую считать, что строка в CP1251 на самом деле есть строка в utf-8 и конвертировать ее по правилам, принятым для конвертации utf-8 в CP1251.



$unconverted0 = $utf8->to_win_r($text);
echo('

to_win (исходник):
'.$unconverted0.'

'
); // to_win (исходник): // #65##195##325##168##455##585##715##845##975#ё#1235##1365##1495##1625##1883##1757##2015##6306##18789##184##31272##47916##60399##470259##1535159##2600827##3141631# .#185#. // Все ожидаемо. Просто у меня класс так устроен, что при невозможности // конвертации в CP1251 возвращает не "?", а #код символа#. // Строку русских символов нельзя перевести в CP1251 по правилам как если бы она была в // utf-8. Ошибка! ?>

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

Все, что дальше, относится к опытам над PCRE.


// Скриптовый файл в CP1251

// Строка для опытов:
$text = 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ abcdefg 4абвгдеёжзиклмнопрстуфхсчшщьыъэюя .№.';

// Она же в utf-8
$converted1 = $utf8->to_utf8_r($text);

echo $text.'
'
; // АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ abcdefg 4абвгдеёжзиклмнопрстуфхсчшщьыъэюя .№. // '#[\xc0-\xcf]#' равноценен '#[А-П]#' var_dump(preg_match('#[\xc0-\xcf]#', $text)); // 1 (вполне естественно) var_dump(preg_match('#[\xc0-\xcf]#', $converted1)); // 0 // Таких символов в строке не встречается // Русские буквы Р или С var_dump(preg_match('#[РС]#', $converted1)); // 1 // Смысл последних этих опытов - показать, что в том случае, когда шаблон в CP1251, // функция preg_match() ищет в кодировке CP1251. То есть она не понимает, что ей дали // русский текст в utf-8 и относится к нему как к отдельным байтам. // Закрепим наши подозрения: var_dump(preg_match('#[А-П]#', $converted1)); // 0 var_dump(preg_match('#[А-П]#', $text)); // 1 // -------------------------------------------------- // попробуем заставить систему считать, что у нас шаблон в utf8 var_dump(preg_match('#[А-П]#u', $converted1)); // PHP Notice // ошибка компиляции регулярного выражения. Значит не получилось! // Ключик не конвертирует шаблон, а только трактует. (Меня не удивило) // Подсунем системе шаблон в utf-8 (файл у нас в CP1251) $utf8_pattern = $utf8->to_utf8_r('#[А-П]#'); var_dump(preg_match($utf8_pattern, $converted1)); // 1 (найдено) var_dump(preg_match($utf8_pattern, $text)); // 1 (найдено) // А вот эти результаты мне уже не очень понятны. // Ну предположим, положительный результат поиска можно еще понять, // когда шаблон в utf-8 и строка в utf-8. // Но успешный поиск в случае, когда шаблон в utf-8, а строка в CP1251 // уже интересен. Похоже, что внутри системы что-то все-таки перегоняется из // одной кодировки в другую // И попробуем использовать ключик "u" $utf8_pattern = $utf8->to_utf8_r('#[А-П]#u'); var_dump(preg_match($utf8_pattern, $converted1)); // 1 var_dump(preg_match($utf8_pattern, $text)); // false // В последнем случае нет никакого нотиса. Но возвращена Ложь, а значит произошла // ошибка. Вспоминаем опыт конвертации строки CP1251 в CP1251, как будто из utf8. // И вот в этом случае я готов принять оба результата. // Вполне возможно, что непонятные результаты (когда шаблон в utf-8, а строка в // CP1251 просто случайно дают успех. // А вот так? $utf8_pattern = $utf8->to_utf8_r('#[\xc0-\xcf]#u'); var_dump(preg_match($utf8_pattern, $converted1)); // 0 var_dump(preg_match($utf8_pattern, $text)); // false // Видимо нужно в шаблоне давать шестнадцатиричный код русского символа в utf-8. // для буквы "А" это 410 (hex) // Убеждаемся, что это так $utf8_pattern = $utf8->to_utf8_r('#[\x410-\x41f]#u'); // тот же диапазон А-П var_dump(preg_match($utf8_pattern, $converted1)); // 1 var_dump(preg_match($utf8_pattern, $text)); // false // А без 'u'? $utf8_pattern = $utf8->to_utf8_r('#[\x410-\x41f]#'); var_dump(preg_match($utf8_pattern, $converted1)); // 1 var_dump(preg_match($utf8_pattern, $text)); // 1 // Вот пожалуйста! Опять неочевидный результат. // Очевидно только то, что #[\x410-\x41f]# и #[А-П]# одно и то же (если в utf-8). exit; ?>

И наконец, получается, что тот пример, где поиск возвращает ложь без нотисов, вполне годится для определения того, можно ли и нужно ли конвертировать файл в utf-8. То есть, если возвращена ложь, то значит в тексте есть символы, которые можно конвертировать и текст нуждается в конвертации. А если возвращен ноль (шаблон строк не найден)? Тогда конвертировать ненужно. Это бесполезно, поскольку в тексте нет символов, которые будут конвертированы. А если возвращена единица? Значит файл с большой вероятностью уже конвертирован.

Единственное, нужно, использовать шаблон '#[А-я№Ёё]#u'. Это все символы, которые конвертируются моим классом.

Кстати, сам класс конвертации нужен? Напишите мне в комментарии прямо здесь (без регистрации и капчей) и я его выложу.
Статья создана 16.08.2013
Похожие материалы - отбираем по ключевым словам