Начиная с Perl версии 5.6 в интерпретаторе была реализована поддержка Unicode на уровне языка. В данном тексте пойдет речь о том, зачем это нужно, как это можно использовать, и с какими проблемами вам придется столкнуться...
Ответ читаем здесь.
UTF — Unicode Transformation Format — формат преобразования Unicode. UTF — это алгоритмичное преобразование любого Unicode символа (code point) в уникальную последовательность байтов. Стандарт ISO/IEC 10646 использует термин "UCS transformation format" вместо UTF. Оба термина почти синонимы.
Каждый UTF формат обратим, то есть поддерживает преобразование из последовательности Unicode-символов и обратно без потерь, то есть: в результате преобразования любой последовательности Unicode-символов S в последовательность байтов и обратно мы получим опять S. Для достижения возможности преобразования без потерь в обе стороны, UTF формат должен определять преобразование для всех символов Unicode, даже невалидных.
Вольный сокращенный перевод What is a UTF?
Поддержка Unicode в Perl 5.6 привязана к операциям, а не к данным, что требует использования прагмы "utf8" и только тогда, когда программист уверен, что все данные в кодировке UTF-8. Также, Perl 5.6 не поддерживает Unicode в регулярных выражениях, что очень сильно ограничивает круг возможностей.
Я знаю слишком мало о применении Unicode в 5.6, так что и писать об этом не могу. Все примеры и текст ниже касаются Perl 5.8.x. Eсли кто хочет, то может осветить особенности 5.6. Закиров Руслан[досье]
Любая структура данных (скаляр, массив, хеш...) в Perl имеет набор флагов, которые отражают те или иные свойства объектов. Для реализации полноценной поддержки Unicode был выбран формат UTF-8 и введен одноименный флаг. Все функции, которые так или иначе работают со строками, учитывают флаг и интерпретируют данные по разному в зависимости от его значения. К таким функциям относятся: length, chr, ord, substr, join, регулярные выражения и прочие. Следующий пример демонстрирует различия в поведении (используемый модуль Encode
включен в дистрибутив Perl начиная с Perl 5.8):
use Encode qw(encode decode is_utf8); my $octets = 'тест'; print "not " if is_utf8($octets); print "ok - \$octets has no UTF-8 flag\n"; my $string = decode('cp1251', $octets); # Encode::_utf8_off($string); print "not " unless is_utf8($string); print "ok - \$string has UTF-8 flag\n"; for(my $i = 0; $i < length($string); $i++) { printf "octet: %#x\t", ord(substr($octets, $i, 1)); printf "char: %#x\n", ord(substr($string, $i, 1)); }
вывод:
ok - $octets has no UTF-8 flag ok - $string has UTF-8 flag octet: 0xf2 char: 0x442 octet: 0xe5 char: 0x435 octet: 0xf1 char: 0x441 octet: 0xf2 char: 0x442
Одна и та же информация в двух форматах: последовательность байтов (октетов) в кодировке cp1251 с отключеным флагом и последовательность символов в кодировке UTF-8 с установленным флагом. Функция length в обоих случаях возвращает одно и то же значение, потому что в cp1251 для хранения одного символа используется один байт, а для строк без флага возвращается именно количество байт. Различия в поведении функций ord и substr видны явно. В целях эксперимента попробуйте отключить флаг UTF-8, для этого раскоментируйте строку Encode::_utf8_off($string);
.
Как уже было сказанно выше, Perl преобразует из "старых" кодировок в UTF-8, для всех преобразований используется модуль Encode, то есть от этого модуля зависит сможете вы использовать преимущества автоматического преобразования данных.
Для каждой кодировки, которая поддерживается, определенно "каноническое" имя и возможно ряд синонимов. Так же названия кодировок не чуствительны к регистру букв, а пробелы заменяются на дефис.
Различные кодировки сгруппированы в модули, например поддержка кодировок UTF-* реализованна в модуле Encode::Unicode. Нет необходимости подгружать эти модули самостоятельно, но возможно для редко-используемых кодировок придется установить дополнительные модули со CPAN.
Посмотреть список всех канонических имен всех кодировок можно с помощью
> perl -MEncode -e'print join ", ", Encode->encodings(":all");'
или посмотреть канонические имена поддерживаемые отдельным модулем
> perl -MEncode -e'print join ", ", Encode->encodings("Encode::Unicode");'
Если вы не можете найти в списке необходимую кодировку, то возможно это синоним какой-либо другой кодировки. Можно использовать следующий небольшой скрипт для определения поддерживается ли та или иная кодировка, и является название каноническим или же это синоним:
#!/usr/bin/perl use strict; use warnings; use Encode qw/resolve_alias/; my $enc = shift; usage() unless $enc; my $canon = resolve_alias( $enc ); unless( $canon ) { print "Encoding '$enc' is not supported\n"; exit 1; } if( $canon eq $enc ) { print "Encoding '$enc' is supported(canonical name)\n"; } else { print "Encoding '$enc' is supported(alias for '$canon')\n"; } exit 0; sub usage { print "usage: $0 <encoding>\n"; exit 1 }
пример использования
> ./is_enc_supp.pl usage: ./is_enc_supp.pl <encoding> > ./is_enc_supp.pl koi8-r Encoding 'koi8-r' is supported(canonical name) > ./is_enc_supp.pl windows-1251 Encoding 'windows-1251' is supported(alias for 'cp1251') > ./is_enc_supp.pl unknown Encoding 'unknown' is not supported
Подробно о том какие кодировки поддерживаются смотрите perldoc Encode::Supported.
Существует стандарт описывающий UTF-8
. Стандарт достаточно строгий в отношении того, что считается UTF-8
, а что нет. Например UTF-8
- это последовательность чисел в диапазоне от 0 до 0x10FFFF, но и в этом диапазоне есть запрещенные значения.
До perl 5.8.7 и Encode 2.10 в perl была реализована ограниченная поддержка стандарта, а именно не было полного контроля допустимых значений. С выходом perl 5.8.7 была представлена новая кодировка utf-8-strict
, которая полностью соответствует стандарту UTF-8
и UTF-8
рекомендуемый синоним для данной кодировки. Для желающих использовать старую реализацию осталась utf8
Воспользуемся скриптом из Какие кодировки поддерживаются в Perl:
> perl ./is_enc_supp.pl UTF-8 Encoding 'UTF-8' is supported(alias for 'utf-8-strict') > perl ./is_enc_supp.pl utf-8 Encoding 'utf-8' is supported(alias for 'utf-8-strict') > perl ./is_enc_supp.pl 'utf 8' Encoding 'utf 8' is supported(alias for 'utf-8-strict')
но
> perl ./is_enc_supp.pl UTF8 Encoding 'UTF8' is supported(alias for 'utf8') > perl ./is_enc_supp.pl 'utf8' Encoding 'utf8' is supported(canonical name).
Нельзя использовать Unicode в следующих ситуациях (актуально для 5.8.x):
Прагма позволяет указать, что код программы написан в кодировке UTF-8. Строковые константы, константные шаблоны в регулярных выражениях и имена переменных будут рассматриваться как строки в UTF-8 кодировке.
Прагма utf8
не влияет на работу потоков ввода/вывода (см. прагмы encoding и open).
Прагма так же не оказывает влияния на интерпретацию строк со сброшенным флагом UTF-8 (см. Конкатенация строк с флагом и без флага).
Многофункциональная прагма. Позволяет явно указать кодировку в которой написан ваш скрипт, например cp1251 или любую другую, которую поддерживает модуль Encode. Также вы можете управлять поведением стандартных потоков ввода/вывода (STD{IN|OUT}). И последнее, в случаях, когда интерпретатору perl необходимо преобразовать строку без UTF-8 флага в строку с флагом, то указанная кодировка будет использована для преобразования. Рассмотрим каждый из трех пунктов по отдельности.
#!/usr/bin/perl -w use strict; use encoding 'cp1251', STDOUT => 'koi8-r'; # cp1251 text my $str = "тест"; printf "%#x\n", ord($1) while $str =~ /(\w)/g; print "$str\n";
Вывод:
0x442 0x435 0x441 0x442 ФЕУФ
Вывели коды букв и получили UTF-8 (первые четыре строки). В последней строке получили слово 'тест' в кодировке 'koi8-r', выведенное на 'cp1251' терминал.
use open IN => ":crlf", OUT => ":bytes"; use open OUT => ':utf8'; use open IO => ":encoding(koi8-r)"; use open IO => ':locale'; use open ':utf8'; use open ':locale'; use open ':encoding(cp1251)'; use open ':std';
Прагма open - это вспогательный интерфейс для определения "уровеней" по умолчанию для всех потоков ввода/вывода. Вместо термина "уровень (layer)" так же используется термин "дисциплина (discipline)". В любых двух-аргументных вызовах open()
, readpipe()
(он же qx//
) и в подобных операторах, находящихся в пределах лексической области видимости этой прагмы, будут использованы указанные значения по умолчанию. Трех-аргументные вызовы не подвержены действию этой прагмы и вы должны указывать уровни самостоятельно.
С помощью подпрагмы IN можно указать из какой кодировки преобразовывать данные из стандартного потока ввода.
С помощью подпрагмы OUT можно указать в какой кодировке интерпретатор должен выводить данные в стандартный поток вывода.
Если вы хотите задать кодировку явно, то используйте :encoding(...)
Если вы хотите чтобы кодировки были выбраны автоматически в соответствии с установленной в системе локалью, то используйте :locale, пример:
$ENV{LANG} = 'ru_RU.KOI8-R'; use open OUT => ':locale'; open(O, ">koi8"); print O chr(0x430); # Unicode CYRILLIC SMALL LETTER A = KOI8-R 0xc1 close O; open(I, "<koi8"); printf "%#x\n", ord(<I>), "\n"; # this should print 0xc1 close I;
Это аналогично этому
use open ':utf8'; use open IO => ':utf8';
или этому
use open ':locale'; use open IO => ':locale';
Если в вызове open() указан явный список уровней, то он добавляется к списку, который объявлен с помощью прагмы open.
Подпрагма :std сама по себе ни на что не влияет, но в сочетании с подпрагмой :utf8 или :encoding она назначает выбранную кодировку для стандартных файловых манипуляторов (STDIN, STDOUT, STDERR).
Например, если объявить прагму :utf8 для потоков ввода и вывода, то :std также установит :utf8 для файловых манипуляторов STDIN, STDOUT, STDERR.
В другом случае, если объявить прагму :encoding(koi8r) для потока вывода, то :std установит koi8r только для файловых манипуляторов STDOUT и STDERR.
Подпрагма :locale неявно включает подпрагму :std
Если платформа поддерживает интерфейс langinfo(CODESET), то возвращаемое им значение используется как базовая кодировка прагмы open.
Если платформа подкачала, но объявлена прагма locale, то для установки базовой кодировки прагмы open используется часть (после .) значения переменных окружения LC_ALL и LANG (в этом по порядке) подходящая для названия кодировки (если она найдена).
Если первые два пункта безуспешны, но в значениях переменных окружения LC_ALL и LANG (в этом по порядке) найдено что-нибудь похожее на UTF-8, то базовой кодировкой прагмы open устанавливается :utf8. Если в значениях переменных окружения (LC_ALL, LC_CTYPE, LANG) найдена строка 'UTF-8' или 'UTF8' (не важно в каком регистре), то базовой кодировкой для стандартных файловых манипуляторов STDIN, STDOUT и STDERR, а также любых файловых манипуляторов открытых позднее устанавливается :utf8
Начиная с версии 5.8.1 была добавлена опция интерпретатора -C
, которая позволяет задать режимы работы с потоками ввода/вывода и массивом @ARGV по умолчанию.
I | 1 | данные поступающие с STDIN будут интерпретироваться как UTF-8 |
O | 2 | вывод данных в STDOUT будет производиться в кодировке UTF-8 |
E | 4 | вывод данных в STDERR будет производиться в кодировке UTF-8 |
S | 7 | I + O + E |
i | 8 | UTF-8 включен по умолчанию для открываемых потоков ввода |
o | 16 | UTF-8 включен по умолчанию для открываемых потоков вывода |
D | 24 | i + o |
A | 32 | предполагается, что элементы @ARGV закодированы в UTF-8 |
L | 64 | все остальные указанные опции будут активированы, только если пользователь использует UTF-8 локаль, например ru_RU.UTF-8 |
Первая группа IOES
влияет на стандартные потоки ввода/вывода, смотрите описание прагмы encoding.
Вторая группа ioD
влияет на поведение создаваемых потоков ввода/вывода. Для достижения подобного эффекта можно использовать трех-аргументный вызов open(), двух-аргументный вызов binmode() и прагму open.
Если опция указана без дополнительных параметров, просто, -C
, то используется -CSDL
.
В perl 5.8.0 такое поведение включено по умолчанию, что может стать причиной некоторых проблем. В perl 5.8.1 и выше по умолчанию используется значение 0.
Переменная окружения PERL_UNICODE
является синонимом опции -С
, но имеет более низкий приоритет и будет проигнорирована, если указана опция.
Во время выполнения кода, вам доступна для чтения "магическая" переменная ${^UNICODE}
, которая отражает состояние описанных выше настроек.
> perl -e 'print "${^UNICODE}\n"' 0 > perl -CIOo -e 'print "${^UNICODE}\n"' > PERL_UNICODE="IOo" perl -e 'print "${^UNICODE}\n"' 19 > PERL_UNICODE=0 perl -CIOo -e 'print "${^UNICODE}\n"' 19 > perl -C -e 'print "${^UNICODE}\n"' > perl -CSDL -e 'print "${^UNICODE}\n"' > PERL_UNICODE="" perl -e 'print "${^UNICODE}\n"' 95
Прагма bytes
позволяет переключить Perl в режим обработки строк как последовательность байтов, а не символов, даже если для строки установлен флаг UTF-8. Прагма отключает действие Unicode флага в пределах данной лексической области видимости.
Следующий пример демонстрирует действие прагмы:
my $str = "\x{442}\x{435}\x{441}\x{442}"; printf "Characters semantic: length is %d\n", length($str); { use bytes; printf "Bytes semantic: length is %d\n", length($str); }
вывод:
Characters semantic: length is 4 Bytes semantic: length is 8
Perl версии ниже 5.6 поставляется без прагмы bytes и если вы хотите сохранить совместимость кода с этими версиями, то можно использовать следующий блок кода:
BEGIN { $INC{'bytes.pm'} = 'foo' if $] < 5.6 }
блок необходимо разместить до первого применения прагмы.
Конвертирует число в символ, где число - это код символа. В версиях 5.8, с приходом unicode, действие функции немного расширилось, а точнее, если аргумнт число больше 255, то функция считает число code point'ом unicode и возвращает соответствующий символ в скаляре с установленным флагом UTF-8.
Пример: конвертирование HTML entities.
my $str = "тест"; $str =~ s/&#x([a-fA-F0-9]+);/"&#". hex($1) .";"/ge; $str =~ s/&#([0-9]+);/chr($1)/ge; print "$str\n";
Конвертирует символ в число. Действие обратное функции chr, для символов с флагом возвращает значение code point'а Unicode.
У функции появился второй аргумент LAYER
, который позволяет указать слои (дисциплины) потока ввода/вывода. Более подробно доступные возможности описываются в другой статье, а в нашем случае наиболее интересны :utf8
, :bytes
и :encoding(...)
.
Пример: Указываем кодировку файла.
my $str = "\x{442}\x{435}\x{441}\x{442}"; open my $fh, ">myfile.txt"; binmode $fh, ":encoding(cp1251)"; print $fh $str; close $fh;
Как уже говорилось unicode меняет поведение регулярных выражений, вернее не меняет, а использует дополнительную мета-информацию о симфолах, которую предоставляет Unicode. Согласно perldoc perlre шаблон \w
совпадает с одним символом алфавита, десятичной цифрой или символом '_', существует достаточно распространенное заблуждение, что это равнозначно шаблону [a-zA-Z0-9_]
, но это конечно же неверно:
perl -e 'use open ":locale"; print "\x{442} - match \\w\n" if "\x{442}" =~ /\w/'
Простой скрипт конвертации текста из одной кодировки в другую:
#!/usr/bin/perl my ($from, $to); use Getopt::Long; GetOptions( "from|f=s" => \$from, "to|t=s" => \$to, ); $_ ||= 'UTF-8' for $from, $to; binmode( STDIN, ":encoding($from)" ); binmode( STDOUT, ":encoding($to)" ); print while <>;
Использование:
shell> iconv.pl -f cp1251 file_name.txt shell> cat file_name.txt | iconv.pl -f cp1251 shell> find dir_name/ -type f -name '*.txt' | xargs perl -i.bakup iconv.pl -f cp1251
Веб-сервер, получая запрос от клиента, не имеет информации о кодировке сообщения. Декодирование и интерпретация строковых данных остается на совести програмиста. В HTML::Mason существует достаточно гибкая система плагинов, чтобы написать плагин, который сконвретирует все строки из последовательностей байтов в perl строки.
package MasonX::Plugin::UTF8; use 5.8.0; use strict; use warnings; our $VERSION = '0.01'; use base qw(HTML::Mason::Plugin); use Encode (); sub start_request_hook { my ($self, $context) = @_; return if $context->request->is_subrequest; my $args = $context->args; $_ = Encode::decode( utf8 => $_, Encode::FB_PERLQQ ) foreach @$args; } 1;
Код очень простой, но имеет небольшой недостаток - нельзя передавать данные не в UTF-8, хоть и нужно это редко, но иногда бывает необходимо.
use LWP::UserAgent; use LWP::Charset qw(getCharset); use Encode qw(decode); my $ua = LWP::UserAgent->new; $ua->agent("MyApp/0.1"); my $url = some_url_goes_here; my $req = HTTP::Request->new(GET => $url); my $res = $ua->request($req); die "problem" unless $res->is_success ; my $content = decode(getCharset($res), $res->content); print $content;
Функция ord() также учитывает состояние флага UTF-8 и может вернуть число больше 255.
Стандартным примером может стать код, реализующий "escaping URI":
> perl -e '$str = "\x{442}\x{435}\x{441}\x{442}"; $str =~ s/(.)/sprintf("%"."%"."%02x", ord($1))/eg; print "$str\n"' %442%435%441%442
Информировать разработчиков о проблеме с целью документирования и/или исправления поведения. Если же необходимо со строкой работать, как с последовательностью байтов, то можно использовать прагму bytes.
> perl -e 'print ord("\x{442}")."\n"' 1090
Конкатенация строки, имеющей флаг UTF-8, со строкой, у которой этот флаг снят, приводит к автоматическому преобразованию строки без флага. При этом Perl исходит из того, что строка в кодировке "latin-1" или той кодировке, которая установлена с помощью прагмы "encoding".
Постоянное использование прагмы "encoding" или же тестирование своего и внешнего кода на наличие этой проблемы. Так как первый вариант — это обходной путь и не является в полном смысле решением, то второй вариант более предпочтителен.
use Encode qw(); #use encoding "UTF-8"; #use utf8; my $str = "\x{442}\x{435}\x{441}\x{442}"; print "not " unless Encode::is_utf8 $str; print "ok - строка с установленным UTF-8 флагом\n"; my $nstr = some_bad_function($str); print "not " unless Encode::is_utf8 $nstr; print "ok - новая строка тоже вообщем-то должна быть с флагом\n"; my $cstr = $str.$nstr; print "not " unless Encode::is_utf8 $cstr; print "ok - конкатенация новой и старой строки имеет флаг\n"; print "not " unless $cstr eq $str.$str; print "ok - так как новая строка это копия старой, то и конкатенация должна равняться удвоенной старой строке \n"; exit; # функция возвращает копию аргумента, но без флага UTF-8 sub some_bad_function { my $str = shift; Encode::_utf8_off($str); return $str; }
Обратите внимание, что использование прагмы "utf8" не решает проблемы.
Один и тот же вызов функции join возвращает неправильные результаты, если был последовательно выполнен для строк с флагом UTF-8, а затем без.
Обновить perl до версии 5.8.3 и выше.
Символ в нижнем и верхнем регистре может занимать разное количество байт, поэтому при вызове функции uc для Unicode-строки часто нужно выделить больше памяти, чем используется для строки сейчас. Так как перевыделение (realloc) памяти достаточно трудоемкая операция, то perl пытается выделить необходимый блок сразу, но использует очень пессимистичный подсчет размеров необходимого блока.
Обновить perl до 5.8.7.
> perl -Ilib -MDevel::Peek -e'$a=("a"x 50)."\x{100}"; Dump($a); Dump uc $a' SV = PV(0x804d56c) at 0x80648d4 REFCNT = 1 FLAGS = (POK,pPOK,UTF8) PV = 0x8077588 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\304\200"\0 [UTF8 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x{100}"] CUR = 52 LEN = 53 SV = PV(0x804d4a0) at 0x804d2dc REFCNT = 1 FLAGS = (PADTMP,POK,pPOK,UTF8) PV = 0x8086e48 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\304\200"\0 [UTF8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x{100}"] CUR = 52 LEN = 1990
ucfirst и lcfirst могут испортить данные в unicode строке
Обновится до версии 5.8.8 или использовать patch.