Pull to refresh

Comments 21

В статье есть некоторые ошибки.

Функция utf8::is_utf8() к сожалению показывает не наличие флага у строки, а внутреннее это представление или нет.

Строка «аа» во внутреннем представлении перла без флага выглядит так: \x{d0}\x{b0}\x{d0}\x{b0}
а с флагом вот так: \x{430}\x{430}

И функция utf8::is_utf8() на оба варианта вернёт истину.

В каком виде строка лучше всего смотреть через Data::Dumper.
Берём простой пример

use Data::Dumper;
use Modern::Perl;

my $a = «аа»;
my $z = «яя»;

print Dumper( $a );

utf8::upgrade( $a ); # перевели во внутреннее представление только чтобы показать что оно отличается от флаговой стоки

print Dumper( $a );

utf8::decode( $a );

print Dumper( $a );

my $qq = "$z. $a";

print Dumper( $qq );

Его результат будет таким:
$VAR1 = 'аа';
$VAR1 = "\x{d0}\x{b0}\x{d0}\x{b0}";
$VAR1 = "\x{430}\x{430}";
$VAR1 = "\x{d1}\x{8f}\x{d1}\x{8f}. \x{430}\x{430}"

А то что \x{d1}\x{8f}\x{d1}\x{8f} — это не флаговая строка лучше всего Вам покажет regexp,
в нефлаговых строках \s пересекается с кириллицей по 1 букве в маленьких и заглавных буквах:

my $a = «абвгдеёжзийклмнопрстуфхцчшщъыьэюя»;
utf8::upgrade( $a );
print «1\n» if $a =~ /\s/;

напечатает единицу, а код

my $a = «абвгдеёжзийклмнопрстуфхцчшщъыьэюя»;
utf8::decode( $a );
print «1\n» if $a =~ /\s/;

ничего не напечатает — потому что мы повесили флаг на $a и буква «х» стала единым целым,
а не двумя половинками, одна из которых совпадает с пробельным символом.

PS чтобы использовать Encode::encode, Encode::decode не обязательно подключать модуль Encode,
проще использовать utf8::decode() и utf8::encode() чтобы не засорять namespace модуля/скрипта
Строка «аа» во внутреннем представлении перла без флага выглядит так: \x{d0}\x{b0}\x{d0}\x{b0}
а с флагом вот так: \x{430}\x{430}

И функция utf8::is_utf8() на оба варианта вернёт истину.

Нет.

my $s1 = "x{d0}\x{b0}\x{d0}\x{b0}";
print "s1:", utf8::is_utf8($s1), "\n";
my $s2 = "\x{430}\x{430}";
print "s2:", utf8::is_utf8($s2), "\n";

s1:
s2:1


utf8::upgrade( $a ); # перевели во внутреннее представление только чтобы показать что оно отличается от флаговой стоки


Нет,

use Data::Dumper;
use Modern::Perl;

my $a = "аа";

print length($a);



печатает 4.

Т.к. буква «a» здесь вовсе не буква, а последовательность байтов, которыми эта буква кодируется в UTF-8 (это потому-что нет «use utf8»).

Соответственно utf8::upgrade над этой строкой, интерпретирует эти байты как символы в кодировке Latin1 и меняет внутреннее представление на Unicode (с utf-8 флагом).

Дальше этот пример не изучал.

Функция utf8::is_utf8() к сожалению показывает не наличие флага у строки, а внутреннее это представление или нет.


Может дело в терминологии? utf8 флаг и показывает внутреннее представление. Если он установлен, то строка — это «массив целых чисел» закодированный в UTF-8. Если флага нет — значит «массив целых чисел», где каждый октет — одно число.

(«массив целых числел» чтобы не путать с символами, ведь байты во «внутреннем представлении» могут быть тоже в UTF-8)

В документации прямо и пишут «the internal representation», т.е. внутреннее представление, а не flaged или что-то подобно
Во внутреннем представлении можно писать символы в любой кодировке.
Вот, например, украинские буквы из cp1251:
\xAA\xBA\xA5\xB4\xB2\xB3\xAF\xBF — это єіїґ, маленкие и большие,
и к utf8 они никаким боком не относятся.

Вы даже сами процитировали одну из строк с этим термином.
И этот термин был ещё до появления utf8 в перле — поищите документацию по первым версиям 5-го перла.

> my $s1 = «x{d0}\x{b0}\x{d0}\x{b0}»;

Это некорректное присвоение, т.е. Вы не то присвоили, это Dumper так выводит стоки.
Надо было
my $s1 = 'аа';
utf8::upgrade($a);

Соответсвенно и результат не тот.

Строка в utf8 с флагом — это именно символы, а без флага — байты, это тоже можно проверить regexp-ом
В документации прямо и пишут «the internal representation»

utf-8 флаг это и есть единственное различие во внутреннем представлении строк. Пишут «internal representation» т.к. не хотят чтобы люди думали о utf8 флаге (его использование считается опасным, про это и пост)

Это некорректное присвоение, т.е. Вы не то присвоили, это Dumper так выводит стоки.

Он именно так выводит строки, чтобы их можно было прочитать обратно в Perl. Формат его вывода — perl.

Надо было
my $s1 = 'аа';
utf8::upgrade($a);

Я уже написал выше, что этот код лишен смысла. Он фактически конвертирует UTF-8 из Latin1 в UTF8. (в дальнейшем получившийся результат иногда правильно работает, т.к. в бинарном контексте, из соображений совместимости, происходит обратная операция)

Строка в utf8 с флагом — это именно символы, а без флага — байты, это тоже можно проверить regexp-ом

нет, весь пост про то, что это не так)

То что регэксп не работает — это The Unicode Bug (тоже описан в посте, со ссылкой на документацию).

p.s.

уже после написания поста, нашёл похожую статью на английском blogs.perl.org/users/aristotle/2011/08/utf8-flag.html

p.p.s.
полностью увидеть внутреннее представление скаляра поможет Devel::Peek Dump, там точно видно внутреннее представление строки (правда не уверен, что Data::Dumper где-то хуже)
> То что регэксп не работает — это The Unicode Bug

Для regexp-а строки 'аа' и x{d0}\x{b0}\x{d0}\x{b0} выглядят одинакого — он видит байты, не символы.

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

В mysql, например, из-за этого пришлось вводить разные функуции: длинна в байтах leght(), в символах — char_leght()
> нашёл похожую статью на английском

Просто ещё одна неверная интерпретация из-за функции utf8::is_utf8()
Именно она и utf8 valid вносят кучу неразберихи и непонимания как оно работает внутри.
В результате люди в коде делают баги — нередко видел что после utf8::is_utf8() думают что в строке utf8-символы,
а это не всегда так (да и сам лет 5 назад такие ошибки допускал).
Это один из авторов Perl написал.
Ну значит он один из тех кто создаёт терминологическую путаницу.

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

Это заблуждение я и хотел развеять своим постом. Где говорят? Пруф?
Просто пообщайтесь с людьми.

Не редко программисты даже не знают что могут получить строку, на которую среагирует is_utf8(), но там будут байты.

Я летом у на работе нашёл (и потом выкинул) код, который имел автоопределение utf8 по такому алгоритму:
получили строку $a
eval{ decode($a) }
проверка на is_utf8, если прошло — значит считаем что теперь у нас utf8 символы

А люди, которые использовали его код не могли понять почему он иногда глючит.
> Дальше этот пример не изучал.

Я изучал — с байтами upgrade/downgrade работают, переводя строки между внетренним и обычным представлением строки, а вот символы бывают только во внутреннем, поэтому upgrade/downgrade для них бесполезны.

Ещё более интересны encode/decode.

utf8::decode может поднять строку только до символов и останавливается, а вот utf8::encode может бесконечно менять строку

код:
my $a = «ая»;

print Dumper( $a );
utf8::decode( $a );
print Dumper( $a );
utf8::decode( $a );
print Dumper( $a );
utf8::encode( $a );
print Dumper( $a );
utf8::encode( $a );
print Dumper( $a );
utf8::encode( $a );
print Dumper( $a );
utf8::encode( $a );
print Dumper( $a );

результат:
$VAR1 = 'ая';
$VAR1 = "\x{430}\x{44f}";
$VAR1 = "\x{430}\x{44f}";
$VAR1 = 'ая';
$VAR1 = 'аÑ';
$VAR1 = 'ðÃÂ';
$VAR1 = 'ÃÂðÃÂÃÂ';

decode() строки вида 'аÑ'; и др. понимает и приводит из к нормальному виду, что бывает полезно.
> Дальше этот пример не изучал.
Я изучал

Если вы запостите этот пример авторам perl (чтобы проиллюстрировать что-нибудь, в багрепорте ит.д.), никто его не будет изучать дальше этих строчек, пока Вы не объясните зачем конвертировать UTF-8 текст из Latin1 в UTF-8… Если эта операция лишена смысла, то этот пример кода смотреть не будут, т.к. там баг.
PS чтобы использовать Encode::encode, Encode::decode не обязательно подключать модуль Encode,
проще использовать utf8::decode() и utf8::encode() чтобы не засорять namespace модуля/скрипта

utf8::decode не обрабатывает ошибки, что чревато падением скрипта в другом месте, испорченными данными, и, может, security проблемами.

В документации есть строчки
Note that this function does not handle arbitrary encodings. Therefore Encode is recommended for the general purposes; see also Encode.
Да, действительно, Data::Dumper выводит строки в формате perl.
И для некоторых строк это означает потерю UTF-8 флага (совершенно законную).
см. perldoc

Так что давайте заново.

Фраза

Строка «аа» во внутреннем представлении перла без флага выглядит так: \x{d0}\x{b0}\x{d0}\x{b0}


1. не имеет смысла, если Вы приводите вывод Data::Dumper
2. не правда, если толковать её буквально: $s="\x{d0}\x{b0}\x{d0}\x{b0} "

И функция utf8::is_utf8() на оба варианта вернёт истину.

конечно, потому что у обоих установлен utf-8 флаг. Собственно определение UTF-8 флага — это то что возвращает эта функция.
Так же используйте Devel::Peek вместо Data::Dumper

То, что Вам Data::Dumper выводит на экран, уже может быть лишино информации о UTF-8 флаге.

p.s. и я уже говорил, про то что Ваш пример делает что-то не то — конвертирует utf8 из latin1 в utf8.
В Encode.pm описание is_utf8 — "...Tests whether the UTF8 flag is turned on in the string. If check is true also checks whether string contains well-formed UTF-8", тогда как utf8::is_utf8 (в utf8.pm) — «Test whether string is in UTF-8 internally. Functionally the same as Encode::is_utf8().»

Если посмотреть исходный код. Encode::is_utf8 (Encode.xs)– использует также bool Perl_is_utf8_string(const U8 *s, STRLEN len).

А utf8::is_utf8 — это в universal.c
    {"utf8::is_utf8", XS_utf8_is_utf8, NULL},
XS(XS_utf8_is_utf8)
{
     dVAR;
     dXSARGS;
     if (items != 1)
	 croak_xs_usage(cv, "sv");
     else {
	SV * const sv = ST(0);
	SvGETMAGIC(sv);
	    if (SvUTF8(sv))
		XSRETURN_YES;
	    else
		XSRETURN_NO;
     }
     XSRETURN_EMPTY;
}

Если сравнить то и там и там используется SvUTF8, но в модуле Encode дополнительно выполняется проверка
    if (RETVAL &&
        check  &&
        !is_utf8_string((U8*)SvPVX(sv), SvCUR(sv)))
        RETVAL = FALSE;

Само определение SvUTF8 есть в sv.h
#define SvUTF8(sv)		(SvFLAGS(sv) & SVf_UTF8)
#define SVf_UTF8        0x20000000  /* SvPV is UTF-8 encoded
#define SvFLAGS(sv)	(sv)->sv_flags

Типы в Perl определяются структурами типа SV (см. sv.h), которые имеют поле sv_flags. Как видим, макрос SvUTF8 выполняет проверку установлен ли флаг is UTF-8 encoded и больше ничего.

Таким образом, utf8::is_utf8 просто проверяет установлен ли этот бит (флаг), который конечно же можно устанавливать с помощью SvUTF8_on (ниже побитовый or, который это делает в макросе)
#define SvUTF8_on(sv)		(SvFLAGS(sv) |= (SVf_UTF8))

а вот Encode::is_utf8 еще и выполняет is_utf8_string (файл utf8.c) о чем было замечено vadiml, которая «…Returns true if the first bytes of string form a valid UTF-8 string, false otherwise….» Для проверки функция старается использовать макрос UTF8_IS_INVARIANT, который определен в utf8.h, иначе более медленный вариант в ветке else.

Я думаю, вывод такой, utf8::is_utf8 — работает очень быстро, но ее не следует использовать на данных получаемых извне, а Encode::is_utf8 – более медленный вариант, но зато выполняет проверку.
В Encode.pm описание is_utf8 — "...Tests whether the UTF8 flag is turned on in the string. If check is true also checks


да, интересно, не замечал этого различия.
Perl_is_utf8_string действительно проверяет является ли строка валидным UTF-8.

Думаю Encode::is_utf8 с флагом check эквивалентно utf8::valid().

Я лично вижу тут различные функции. utf8::is_utf8() — проверит UTF-8 флаг у строки. utf8::valid() — проверить
является ли строка валидной UTF-8.

Опять же, наличие или отсутствие UTF-8 флага или даже валидности UTF-8 ничего не говорит о данных (бинарные vs текстовые). Произвольная JPEG картинка в Perl
может быть с UTF-8 флагом, и быть валидным UTF-8. При этом при любых действиях с ней, конвертироваться обратно в байты (можно посчитать md5 от неё, и он будет md5 от байтов, а не от UTF-8 «представления»)

Я бы рассматривал UTF-8 строку как «массив целых чисел». Т.е. Perl может использовать UTF-8 формат для хранения случайных «чисел», без оглядки на стандарт Unicode.

Кстати, по этой причине, utf8::valid() не надёжен для валидации текста в UTF-8. Он может вернуть истину для данных, которых нет в стандарте Unicode.

пруф:
perldoc.perl.org/perlunifaq.html#What%27s-the-difference-between-UTF-8-and-utf8?
rt.perl.org/Public/Bug/Display.html?id=43294#txn-1256493

Лучше использовать Encode::decode(). Или (здесь пока не уверен), самая надёжная штука, это модуль search.cpan.org/perldoc?Unicode%3A%3AUTF8
Думаю Encode::is_utf8 с флагом check эквивалентно utf8::valid().
— получается не так. В Encode::is_utf8 с флагом check выполняется
    RETVAL = SvUTF8(sv) ? TRUE : FALSE;
    if (RETVAL &&
        check  &&
        !is_utf8_string((U8*)SvPVX(sv), SvCUR(sv)))
        RETVAL = FALSE;

Получается, что TRUE если есть установленный флаг и не выполняется условие ниже. Условие ниже не выполняется также когда check=0, в этом случае также не будет выполнятся проверка !is_utf8_string. То есть, когда check=0 – то это полный аналог utf8::is_utf8 (также если флаг не установлен). Только в случае если check=1 и установлен флаг Encode::is_utf8 выполняет проверку is_utf8_string. То есть, возвращаем true когда есть флаг + проходим проверку.

По utf8::valid – если флаг установленный выполняем проверку. Если она проходит – значит ОК. Если флаг не установлен – возвращаем ОК без проверки. Название вводит в заблуждение, наверное, хотя так и должно быть – “… [INTERNAL] Test whether STRING is in a consistent state regarding UTF-8. Will return true if it is well-formed UTF-8 and has the UTF-8 flag on or if STRING is held as bytes (both these states are 'consistent'). Main reason for this routine is to allow Perl's testsuite to check that operations have left strings in a consistent state. You most probably want to use utf8::is_utf8() instead.”

    {"utf8::valid", XS_utf8_valid, NULL},

XS(XS_utf8_valid)
{
     dVAR;
     dXSARGS;
     if (items != 1)
	 croak_xs_usage(cv, "sv");
    else {
	SV * const sv = ST(0);
	STRLEN len;
	const char * const s = SvPV_const(sv,len);
	if (!SvUTF8(sv) || is_utf8_string((const U8*)s,len))
	    XSRETURN_YES;
	else
	    XSRETURN_NO;
    }
     XSRETURN_EMPTY;
}

Получается, что TRUE если есть установленный флаг и не выполняется условие ниже

Да, действительно. И даже в документации так же написано.

Если флаг не установлен – возвращаем ОК без проверки.


А мне кажется это более логичным. Если флаг не установлен, значит все «символы» в строке — от 0 до 255.

Will return true if it is well-formed UTF-8 and has the UTF-8 flag on or if STRING is held as bytes (both these states are 'consistent').


Так как внутреннее представление строки не важно при всех операциях,
и строка байтов может быть проагрейжена до UTF-8 строки (и будет ей равна — «eq»), то строка байтов всегда является валидной строкой.

Собственно эта функция всё равно не пригодна для валидации Unicode UTF-8 символов, и единственное её назначение — проверка внутреннего состояния строк Perl.

Encode::is_utf8 же вроде как создана для валидации текста, с другой стороны не пригодна для этого (см. мой предыдущий коммент выше), так что её поведенее кажется мне более странным.
Т.е. перекодировка туда-обратно не меняет данные (это ожидаемо), но устанавливает UTF-8 флаг.
(впрочем, такое поведение decode() противоречит его собственной документации, которая, в свою очередь, противоречит идее, что никакой документации и гарантий относительно utf-8 флага в ASCII данных быть не должно)

Исходя из реализации в Encode.xs — Method_decode_xs там явно выполняется в конце SvUTF8_on(dst); Я не нашел «явного» исключения для ASCII,EBCDIC. Все мои попытки в Perl коде заставить не выставлять флаг utf8 для decode, также ни к чему не привели.

Судя по-всему это неточность в документации, что уже обсуждалось здесь — rt.cpan.org/Public/Bug/Display.html?id=34259, в том числе и автором поста :).
Я думаю, многие были введены в заблуждение формулировкой, что внутренним форматом (internal format) в Perl является utf-8. Эта фраза встречается и в документации к модулям, например в описании Encode::decode — "..this function returns the string that results from decoding the scalar value OCTETS, assumed to be a sequence of octets in ENCODING, into Perl's internal form", который конвертирует в utf8 + выставляет флаг utf8.

Есть хороший ответ на данный вопрос — www.perlmonks.org/?node_id=551676#i_lost_track__what_encoding_is_the_internal_format_really — "… by default, the internal format is either ISO-8859-1 (latin-1), or utf8, depending on the history of the string.."

В коде
use strict;
use warnings;
use Devel::Peek;

my $str = "Hello";
print Dump ($str);

для $str используется в качестве внутреннего формата Latin-1. Если мы добавим в этот же код use utf8; все равно будет использоваться Latin-1.

Этот код, написанный в utf-8 опять же использует Latin-1. Мы не дали подсказки с помощью use utf8; для интерпретатора, что же такое $str. Когда мы укажем use utf8 — будет использоваться UTF-8.
use strict;
use warnings;
#use utf8;
use Devel::Peek;

my $str = "Привет";
print Dump ($str);


Этот код, набранный в cp1251, будет использовать UTF-8 в качестве внутреннего формата
use strict;
use warnings;
use encoding('windows-1251');
use Devel::Peek;

my $str = "Привет";
print Dump ($str);


А это код (который набран в utf-8), казалось бы должен использовать utf8 в качестве внутреннего формата, но это не так — используется Latin-1, наверное из соображений эффективности :).
use strict;
use warnings;
use utf8;
use Devel::Peek;

my $str = "Hello";
print Dump ($str);


Всё правильно, только я вместо «внутреннего формата Latin-1» представляю себе что внутренний формат «байты», который интерпретируется как Latin1 в строковом контексте.
Sign up to leave a comment.

Articles