Кодировки
версия для печатиМои сайты сделаны в кодировке windows-1251, включая БД. Возникла необходимость получить файл в кодировке UTF-8 (собираю dump-файл базы). Как оказалось, преобразование из одной кодировки в другую - задача не тривиальная. В этой статье я попытаюсь разложить по полочкам, что к чему. Поле деятельности: Apache/2.2.15 (Win32) PHP/5.3.5. Во внимание беру только две кодировки, UTF-8 и Windows-1251 (aka cp1251), но многое из сказаного относится к преобразованиям в любых кодовых страницах.
Прежде чем хвататься за клаву и "превращать воду в вино"..
часть Рекомендательная
Настройка кодировки PHP-препроцессора (php.ini или .htaccess), параметр default_charset. Он нужен для указания браузеру, в какой кодировке передается ответ (не обязательно html-страница). Это указание передается в заголовках (HTTP headers) ответа. Причем в мета-теге html-страницы может быть другая кодировка или не быть вообще, это отдельная история. Если в заголовках кодировка не указана, нормальный браузер (типа FireFox) пытается сам ее определить по содержимому или по мета-тегу, если он есть.
Аналогичная картина наблюдается для web-сервера Apache. Кодировка ставится директивой addDefaultcharset. Ее можно указывать в конфиге httpd-vhosts.conf (секция <directory>, потребуется перезапуск сервера), или в .htaccess сайта. Зачем нужна отдельная директива Apache? Это очевидно: на PHP свет клином не сошелся, web-сервер может и без него формировать ответы на запросы браузера, вот для таких ответов можно указывать конкретную кодировку.
Приведу несколько примеров получающихся заголовков.
Настройки PHP: windows-1251 (php.ini: default_charset=windows-1251)
...
Content-Type: text/html; charset=windows-1251
Настройки Apache: UTF-8 (.htaccess: addDefaultcharset UTF-8)
HTTP/1.1 200 OK...
Content-Type: text/html; charset=UTF-8
Настройки PHP и Apache: не указана кодировка
HTTP/1.1 200 OK...
Content-Type: text/html;
часть Функциональная (PHP)
Переключить кодировку PHP-препроцессора на время выполнения скрипта можно так:
ini_set('default_charset','UTF-8');С другой стороны, перед передачей ответа можно явно указать заголовок с кодировкой:
header ('Content-Type: text/html; charset=windows-1251');Кодировка файла скрипта. PHP-файл - это обычный текстовик, все символы в нем кодируются набором байт с учетом установленной кодировки текста. Если в скрипте используются функции типа echo(), fwrite() и т.п., причем текст их параметров записан в этом же скрипте, то здесь так же важна кодировка самого файла скрипта! Пример файла some.php:
<?php echo 'Pусский текст в UTF-8'; ?>
Чтобы в браузере этот текст был действительно в UTF-8, кодировка файла с приведеным кодом должна быть UTF-8. Посмотреть и исправить кодировку файла можно, например, в Notepad++. У меня php-редактор (Blumentals Rapid PHP 2010) так же устанавливает кодировку для новых файлов.
БД, внешние файлы. Если кодировка текста, полученного из таблицы/файла/сайта, отличается от нужной, то придется перекодировать. В этом месте наконец-то можно использовать функции типа iconv(), utf8_encode(), ob_iconv_handler(), mb_output_handler() и т.п., а так же пользовательские функции перекодирования.
Назначение всех этих функций разное. Например, utf8_encode() поддерживает только следующее преобразование: ISO-8859-1 (aka Latin-1) > UTF-8. Две других функции, ob_iconv_handler() и mb_ output_handler(), можно использовать только как callback-функции для буфферизированного вывода (этот момент до меня долго доходил). Пример использования: здесь источником текста является сам скрипт (кодировка файла: UTF-8), настройки web-сервера и PHP не суть важны, в браузере можно переключиться.
echo 'русский текст в UTF-8<BR>';
iconv_set_encoding("internal_encoding", "UTF-8"); //"входящая" кодировка
iconv_set_encoding("output_encoding", "WINDOWS-1251");//кодировка перед выдачей из буфера
ob_start('ob_iconv_handler'); //назначаем доп. обработчик
echo 'русский текст в WINDOWS-1251<BR>';
ob_end_flush();
Результатом в браузере будут две строки, первая будет читаться при выборе кодировки страницы "UTF-8" (см. в настройках браузера), вторая - "Кириллица (Windows-1251)". Это был учебный пример, реальное применение перекодирования "на лету": нужно вернуть пользователю html-страницу в другой кодировке.
Замечание: если callback-функции в случае ошибки вернут "false" вместо новой строки, вывод информации не остановится и сообщений не будет, получится текст без перекодирования.
В моей задаче (получение UTF8-файла) пришлось применять функцию iconv() к каждой записываемой в файл строке.
Создание файла с нужной кодировкой. Тут вроде особых проблем нет, используем iconv() или сразу пишем в нужной кодировке. Зато есть ньюанс с UTF-8 файлами. Следующий код создает файл с кодировкой UTF-8 (файл скрипта в той же кодировке):
$text="русский текст, english text\nВторая строка";
$file=fopen('some.txt','w+b');
//fwrite($file, pack("CCC",0xEF,0xBB,0xBF)); //Пишем сигнатуру UTF-8
fwrite($file, $text);
fclose($file);
Некоторые программы не корректно определяют кодировку полученного файла. Выход: дописывать сигнатуру UTF-8 в начале файла. Для этого нужно раскоментировать строку (3). Что такое "сигнатура UTF-8", цитата из Википедии: "Порядок байтов (BOM, сигнатура) Многие программы Windows (включая Блокнот) добавляют байты 0xEF, 0xBB, 0xBF в начале любого документа, сохраняемого как UTF-8. Это метка порядка байтов Юникода (англ. Byte Order Mark, BOM), также её часто называют сигнатурой (соответственно, UTF-8 и UTF-8 with Signature). По наличию сигнатуры программы могут автоматически определить, является ли файл закодированным в UTF-8, однако файлы с такой сигнатурой могут некорректно обрабатываться старыми программами, в частности xml-анализаторами. Такие редакторы, как Notepad++, Notepad2 и Kate позволяют явно указывать, следует ли добавлять сигнатуру при сохранении UTF-файлов."
Post Scriptum
Нашел на форумах пользовательскую функцию преобразования windows-1251 <-> UTF-8. Может пригодится когда-нибудь. У меня она работала криво в направлении Win -> UTF. Пофиксил, все работает:)
function Encode($str, $type='w2u'){
// 'u2w' - кодировать из UTF в win
// 'w2u' - кодировать из win в UTF
$conv=array ();
for ($x=128; $x<=143; $x++){
$conv['utf'][]=chr(209).chr($x);
$conv['win'][]=chr($x+112);
}
for ($x=144; $x<=191; $x++){
$conv['utf'][]=chr(208).chr($x);
$conv['win'][]=chr($x+48);
}
$conv['utf'][]=chr(208).chr(129);
$conv['win'][]=chr(168);
$conv['utf'][]=chr(209).chr(145);
$conv['win'][]=chr(184);
if ($type=='u2w') return str_replace ($conv['utf'], $conv['win'], $str);
elseif ($type=='w2u'){
//return str_replace ($conv['win'], $conv['utf'], $str); //баг
$arr=array_combine($conv['win'], $conv['utf']);//корректно работает
return strtr($str,$arr);
}
else return $str;
n}
Баг был в строке (20): функция str_replace() гонит на половине алфавита. Заменяет русскую win-букву на двухбайтное UTF-значение, а потом а этой замене опять заменяет первый байт(!), который в win-1251 так же соответствует букве, на UTF-значение. Причем другая половина алфавита нормально заменяется.
Таблица соответствия Unicode/UTF-8 <-> Windows-1251 (и не только) http://www.utf8-chartable.de
[1oo%, EoF]Понравилась статья? Расскажите о ней друзьям: