Кодировки

версия для печати

Мои сайты сделаны в кодировке 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)

HTTP/1.1 200 OK
...
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]

Понравилась статья? Расскажите о ней друзьям:

Метки: PHP, кодинг

Комментарии
Для работы модуля комментариев включите javaScript


Показать/скрыть правила
Имя
[i] [b] [u] [s] [url]
:-) ;-) :D *lol* 8-) :-* :-| :-( *cry* :o :-? *unsure* *oops* :-x *shocked* *zzz* :P *evil*

Осталось 1000 символов.
Код защиты от спама Обновить код
Каждый комментарий проходит ручную модерацию. 100% фильтрация спама.
Продвижение
Время
Метки