пятница, 18 апреля 2014 г.

Переключение кодировки

Решил я, что пришло время изменить кодировку страниц своего сайта «Заливная рыба», по крайней мере, начать эту работу. Хорошо бы, конечно, для таких случаем иметь Большую Красную Кнопку — голубую мечту Доктора Кто. Написа́л предыдущее предложение, прочитал его и осознал двусмысленность: то ли мечта Доктора голубая из–за её несбыточности, то ли вследствие изрядной (хотя не вполне явной) «голубизны» самого Доктора. Осторожнее нужно быть с эпитетами…

В общем, кнопки нет и в обозримом будущем не предвидится, а процесс перевода предыдущей версии сайта из кодировки CP-1251 в более прогрессивную UTF-8 нужно начинать. Первым делом, взялся за базу данных: в ней уже накоплено сведения о 80 давно подготовленных статьях и таблицы словарей (теги, категории, рубрикаторы). Сначала, с помощью встроенной в панель управления моего хостинг–провайдера утилиты phpMyAdmin скопировал всё содержимое базы данных MySQL к себе на компьютер в один файл («экспорт» называется). Затем вычистил БД, то есть удалил все таблицы вместе с содержимым, после чего поменял кодировку базу на вожделенную utf8-general_ci.

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

Долгое время я пользовался старым добрым Macromedia Homesite+ версии 5.5 — старичок натужно пыхтел, жрал немилосердно внушительные объёмы ресурсов, гадёныш, но привык я к нему (всё–таки 10 лет вместе!), несмотря на некоторые неудобства. Буквально месяц тому назад я вынужден был с ним «распрощаться навеки»: взялся я ваять новый сайт, непременно в кодировке UTF-8, а мой «старикашка» ни сном, ни духом не знает что это такое.

Пришлось искать альтернативу, и она нашлась довольно быстро — Notepad++. Редактор простой, мощный и, что немаловажно, бесплатный (мой старый Homesite без таблэтки работать отказывался). А главное — понимает любые кодировки и, будучи дополнительно укреплён–усилен плагинами (я установил Emmet, QuickText и некоторые другие) оказался не менее функциональным и исполнительным, чем привычная софтина.

В общем, открыл я в NPP (Notepad++) файл экспорта из БД и запустил перекодировку в «UTF-8 без BOM» — секунда дела! Перед тем, как сохранить перекодированный файл пришлось порихтовать свойства таблиц: вместо

) ENGINE=MyISAM AUTO_INCREMENT=148 DEFAULT CHARSET=cp1251;

прописать

) ENGINE=MyISAM AUTO_INCREMENT=148 DEFAULT CHARSET=utf8;

Мне пришлось это сделать 7 раз и только потом сохранить файл. Следующий шаг — загрузка данных обратно в БД («импорт» называется и производится всё через ту же phpMyAdmin)

После этого всё стало гораздо хуже: к неудачному дизайну добавились совершенно нечитаемые «крокозяблы» (Дальний Восток и весь запад их называют моджибакой: mojibake) на всей поверхности страницы — простые вопросительные знаки (мол, что за хрень творится?!) и вопросительные знаки в чёрных ромбиках (навевают какие–то тревожные и угрюмые ассоциации).

Перекодировка всё в том же NPP файлов шаблонов и скриптов, а также замена кодировки страниц с


на


проблему не решили, хотя и принесли некоторое облегчение: кое–какие надписи стали читаемыми. Однако, всё, что «пришло» на страницу из базы данных, представляло собой одни вопросы, то есть, состояло сплошь из вопросительных знаков (моджибака, прости Господи!).

Тогда решил посмотреть, что вообще творится с переменными MySQL. Запустил запрос, получил ответ:

SHOW VARIABLES LIKE 'character_set%'
имя переменной значение
character_set_client utf8mb4
character_set_connection utf8mb4
character_set_database utf8
character_set_filesystem binary
character_set_results utf8mb4
character_set_server latin1
character_set_system utf8
character_sets_dir /usr/share/mysql/charsets/

Оказалось, что кодировка utf8mb4 введена в MySQL начиная с версии 5.5.3 (в начале 2010 года) из–за того, что ранние версии MySQL могли сохранять только 5,88% символов Юникода, в то время как UTF–8 (Unicode Transformation Format, 8-bit — «формат преобразования Юникода, 8-битный») в состоянии отображать все 100%. Проблема в том, что UTF–8 использует для кодировки символов от 1 до 6 байтов (это теоретически, на практике же — от 1 до 4), а MySQL умела сохранять только трёхбайтовый UTF–8.

Стало быть, нужно ещё раз базу данных «перековать» в новую диковинку — utf8mb4. Только, вот какая штука: кодировка windows–1251 однобайтовая, а utf–8 — двухбайтова (для кириллицы, иврита, арабского, греческого, армянского, коптского алфавитов и таких экзотов, как тана, использующегося на Мальдивах и нко — в Гвинее и Мали; кстати, для грузинского алфавита требуется трёхбайтный код, как и для китайского, японского, корейского, индийского). Это значит, что для строки из 100 символов потребуется 100 байт в кодировке cp–1251 и 200 (а то и 300) в кодировке UTF–8.

Дальше — больше (в самом прямом смысле): utf8mb4 использует для хранения каждого символа 4 байта, то есть, для ставшего уже милым для нашего сердца примера с сотней кириллических буковок потребуется 400 байт. Поэтому, простой заменой кодировки тут не отделаешься — придётся ещё и увеличивать размер строковых и текстовых полей в таблицах.

В конечном итоге, получилось так (сравните первый вариант, от windows–1251 и второй, для utf–8 с добавкой от MySQL для одной из таблиц базы данных)

CREATE TABLE `vocabular` (
  `sysn` int(11) unsigned NOT NULL auto_increment,
  `ref` int(11) NOT NULL,
  `defen` varchar(100) NOT NULL,
  `lang` enum('en','ru','la') NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `voc_1` (`ref`,`defen`)
) ENGINE=MyISAM AUTO_INCREMENT=521 DEFAULT CHARSET=cp1251;
ALTER DATABASE `zalivryba` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;

SET NAMES 'utf8mb4';

CREATE TABLE `vocabular` (
  `sysn` int(11) unsigned NOT NULL auto_increment,
  `ref` int(11) NOT NULL,
  `defen` varchar(400)  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `lang` enum('en','ru','la') NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `voc_1` (`ref`,`defen`(235))
) ENGINE=MyISAM AUTO_INCREMENT=521 DEFAULT DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Таким образом:

  • изменена кодировка всей БД, каждой из таблиц и каждого строкового поля (VARCHAR и TEXT) в таблицах;
  • увеличен в 4 раза размер каждого строкового поля;
  • установлены ограничения на размеры некоторых индексов — поскольку размер индекса не может быть более 1000 байт, то для поля, например, `defen` остаётся не более 989 байт (размер `ref` составляет 11 знаков), а это значит, что индексировать можно только первые 989:4 = 247 символов–знаков (ну, и хватит!)

Потом, как советуют бывалые люди, запустил восстановление (REPAIR) и оптимизацию (OPTIMIZE) всех таблиц, — быстро, из панели управления — и, дрожа от нетерпения, бросился смотреть на результат, а там… Ничего не изменилось: на экране была всё та же печальная моджибяковщина…

Очередная проверка переменных среды нарисовала странную картину:

За разъяснениями пришлось обращаться в службу поддержки хостинг–провайдера. Очень вежливые и обходительные специалисты настойчиво втолковывали мне, что они изменить ничего не могут, поскольку серверные установки могут повлиять на сайты всех остальных клиентов — хостинг–то sharing. В общем, ситуация сложилась тупиковая: впору опять всё возвращать в прежнюю кодировку windows–1251 и плакать по ночам от несбывшейся мечты про UTF–8 — не помогла даже очень хорошая статья толкового программера (то, что он является одним из авторов HTML5 Boilerplate, говорит само за себя).

Но решение нашлось, когда я бросил уже прощальный взгляд на мануал MySQL: там, в разделе «10.1.5 Configuring the Character Set and Collation for Applications»:

Приложениям, использующим базу данных, следует конфигурировать свои соединения с сервером при каждом подключении. Это можно сделать путём исполнения запроса SET NAMES 'utf8' после соединения. Этот запрос может использоваться независимо от способа подключения.

Решил напоследок попробовать ещё и этот вариант (а что я теряю?!), запустил в своём PHP–скрипте такую команду:

$db = DbSimple_Generic::connect("mysql://$user:$password@localhost/$name");
$db->query("SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'");

В первой строке кода — подключение к базе данных (я давно использую очень удобную библиотеку Дмитрия Котерова), а во второй — его настройка на utf8mb4 (перешибаются установки сервера character_set_server, collation_connection, collation_server и, наверное, многое другое). После этого всё… заработало!

Всё остальное вообще оказалось детской задачкой: изменил кодировки в заголовках страниц, перекодировал шаблоны, файлы инициализации строковых переменных — деловито и без проблем.

Voilà!

1 комментарий: