http://ll.anew.su/personal/html/

posted: 01/03/2020 at 12:55:11
modified: 02/03/2020 at 19:06:14

Perl и Unicode. Кодирование текста переменных, и внутренний формат Перл. Модуль Encode.

Общие вопросы.

Всем, лютый хэлоу!

Столкнулся с проблемой использования модуля Encode в Perl, поэтому решил наваять небольшую заметку, скорее для себя, но может пригодится кому-то еще. Пишу еще и потому, что в сети не очень много доходчивой информации по работе с Юникодом в Перл на русском языке.

И так, что делать, если необходимо внутри программы использовать строки в разных кодировках? Например, мы имеем скрипт в кодировке cp1251, а внутри скрипта, необходимо поработать со строками в кодировке UTF-8?

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

На первый взгляд, все просто. В нем есть функции Decode и Encode, что по-логике должно означать кодирование и декодирование. Только сразу не понятно, из чего и во что?

Оказывается, из, и во внутренний формат Перл.

Внутренний формат Перл.

И вот здесь начинается самое интересное.

С появлением поддержки юникода в Перл, переменные программ могут храниться не только как последовательность байтов, но и в так называемом внутреннем формате Перл. Что это такое? Внутренний формат Перл, есть не что иное, как текст в кодировке UTF-8. Но, для того чтобы Перл понимал как именно рассматривать строковую переменную, как набор байтов (a sequence of octets) или как последовательность символов, этой переменной устанавливается специальный флаг.

Стандарт Unicode, в отличие от стандартов однобайтовых кодировок, подразумевает, что один символ может быть закодирован с использованием нескольких байтов. Именно поэтому, текст в какой-либо кодировке Юникод, уже нельзя рассматривать как последовательность отдельных байтов, где каждый байт означает один символ.

Но, можно ли хранить текст в кодировке UTF-8 как последовательность байтов? Да, можно. Однако такой текст не будет рассматриваться интерпретатором как внутренний формат и работать с таким текстом в функциях и регулярных выражениях - корректно не удастся, так как Перл по-прежнему будет считать, что работает с текстом в однобайтовой кодировке, где, как было сказано выше, один байт означает один символ.

Чтобы все работало корректно, необходимо текстовой переменной присвоить соответствующий флаг, который сообщит интерпретатору, как именно работать с переменной, как с последовательностью байтов, или же как с последовательностью символов. Т.е. флаг нужен именно для того, чтобы Перл отличал, где он работает с текстом в Юникод, а где просто с текстом в национальной, однобайтовой кодировке. Если флаг есть, Перл будет понимать, что работает с символами Юникода. Если такого флага нет, то Перл будет трактовать строку как простую последовательность байтов.

Естественно, флаг может быть присвоен и переменной, не содержащей валидный набор кодов символов UTF-8 и наоборот, как уже было сказано выше, валидный UTF-8 текст может не иметь соответствующего флага.

С тем, что такое внутренний формат Перл, разобрались. Переходим к практике.

Теперь, о самой перекодировке. Функции Encode и Decode.

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

Допустим, наш файл программы находится в восьмибитной кодировке cp1251. В этом файле есть строка, которая, естественно, так же находится в указанной кодировке. Необходимо отправить эту строку клиенту в виде ответа, например, на AJAX-запрос, который и вызвал cgi-скрипт к жизни. Сегодня, стандарт де-факто для web - это кодировка UTF-8. Если в виде ответа от сервера в браузер придет строка в нашей кодировке (cp1251), то на экране у клиента будет набор не читаемых символов.

Чтобы все прошло как полагается, необходимо перекодировать строку символов в UTF-8, и уже после этого, отправить ее в виде ответа.

Функция encode - переводит текст из внутреннего формата Перл, в ту кодировку, которая нам нужна. Для этого, надо указать нужную кодировку явно.

Но, что делать, если наш текст не во внутреннем формате, а в кодировке cp1251? Если использовать функцию encode для строк с текущей кодировкой, то очевидно, что она отработает совсем не так, как ожидалось, ибо наш текст не находится во внутреннем формате.

Для того, чтобы такого не происходило, необходимо гарантированно перевести строку во внутренний формат.

Поэтому, сначала воспользуемся функцией decode.

Функция decode - переводит текст из нашей однобайтовой кодировки cp1251 во внутренний формат Перл (UTF-8 + флаг). Текущую кодировку строки, также необходимо указать явно.

my $string = 'твой стринг'; #строка содержит текст в восьмибитной кодировке cp1251

my $string = decode('cp1251', $string); #теперь строка содержит текст во внутреннем формате Перл с установленным флагом

Казалось бы, если decode конвертирует из указанной нами кодировки во внутренний формат, а внутренний формат у нас как известно, это UTF-8, то, что мешает тут-же отправить перекодированную строку клиенту?

Оказывается, кое что мешает. Мешает тот самый флаг, который хранится в одной строке со значением переменной. Поэтому сразу, перекодированную строку использовать нельзя, так как вместе с полезной информацией, клиенту отправится и тот самый флаг, который болтается вместе с байтами полезной информации. Вследствие чего, браузер получит неправильный набор кодов UTF-8.

Значит, его надо снять.

Здесь и воспользуемся функцией encode. Напомню, encode переводит строку из внутренней кодировки, в указанную явно. При этом, флаг конечно же снимается, так как для работы напрямую с байтами, никакие флаги не нужны.

encode('UTF-8', $string); #строка переводится из внутреннего формата Perl в указанный явно UTF-8. Флаг снимается.

А теперь сделаем все вместе:

my $string = 'твой стринг'; #строка содержит текст в восьмибитной кодировке cp1251

my $string = encode('UTF-8', decode('cp1251', $string)); # теперь, строка содержит последовательность байтов UTF-8 (без флага)

Таким образом, с помощью функций стандартного модуля Перл, можно перекодировать текст в нужный нам формат и отправить клиенту.

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

Прагма use utf8.

Прагма use utf8 - нужна лишь в том случае, если исходный код сценария, уже закодирован в UTF-8.

Тогда, Перл автоматически считает, что все текстовые переменные уже находятся во внутреннем формате Перл и им присвоен соответствующий флаг.

В иных случаях, эту прагму использовать не надо. Т.е., если к примеру исходный файл сценария набран в кодировке cp1251 - прагму use utf-8 - использовать нельзя, иначе, Перл будет считать, что текстовые константы, встреченные в коде программы, являются строками Юникода и будет работать с ними как с символами Юникода, в то время, как они юникодом не являются.

Это и будет тем случаем, когда флаг Юникода у переменной есть, но нет самого валидного Юникода.

Также, очень часто при работе с Юникодом в Перл, встречается сообщение о wide characters in print. Это значит, что при попытке вывести текст из внутреннего формата на экран или в файл, Прел предупреждает о присутствии символов Юникода в выводимом потоке. Оно и логично, и появляется, если пользователь решит поместить строку закодированную UTF-8, в дескриптор, открытый для записи в однобайтовой кодировке, и получит в файле или на экране, нечто не читаемое.

В этом случае, надо согласовать кодировки, приведя их к одному формату. Как это сделать, уже было рассказано выше, однако, для работы с вводом-выводом текстовых данных, это неоптимальный способ. При записи или чтении файла, желательно использовать уровни (Input and Output Layers), когда файл сразу открывается в нужной кодировке, а перекодирование происходит на лету. Об этом, читайте в документации.

Вместо послесловия.

За всеми подробностями по работе с Unicode в Perl, следует обращаться к официальному руководству по языку Perl.

perlunicode, perluniintro, perlunifaq, perlunitut, utf8

Есть тысяча способов быть очень дурным человеком, не нарушая ни одного закона.