use utf8 環境下で => オペレータの左辺が UTF8 flag on になってしまう
use strict; use warnings; sub Dump { @_ = map { sprintf "'%s'(%s)", $_, utf8::is_utf8($_) ? 'utf8' : 'bytes' } @_; print {*STDERR} join(q{, }, @_), "\n"; } no utf8; Dump( foo => 'bar' ); #=> 'foo'(bytes), 'bar'(bytes)
no utf8;
だもんで,両者とも bytes なのは,まぁあたりまえ。
ところが,これを use utf8;
で動かすと……
use utf8; Dump( foo => 'bar' ); #=> 'foo'(utf8), 'bar'(bytes)
foo
の UTF8 flag が on になってる!
use utf8
にしてたので,任意の文字列リテラルが UTF8 flag on になっても文句はいえないです。しかし,一般の文字列リテラルでは,Latin-1 の character set に収まっている場合,UTF8 flag は on になりません。実際 'bar'
は Latin-1 に収まっているので UTF8 flag は off です。
ということで,回避方法?ですが,=>
オペレータの左辺を,きちんとした文字列様式(つまりクォーテーションで囲うなど)にすると
use utf8; Dump( 'foo' => 'bar' ); #=> 'foo'(bytes), 'bar'(bytes)
両者とも bytes になります。
にて確認しました。
仕様といえば仕様?バグ?
2009-01-29 追記
=>
に限らなかったです。なんというか,生の(文字列とみなされる)リテラルならそうなる感じ?
use utf8; my %h; $h{foo} = 'bar'; $h{'baz'} = 'ban'; Dump(%h); #=> 'baz'(bytes), 'ban'(bytes), 'foo'(utf8), 'bar'(bytes) my $d = <<'END_DOC' foo END_DOC chomp $d; Dump($d) #=> 'foo'(utf8)
ヒアドキュメントはまぁ仕方ないですけど,それ以外のケースは,なんというか直感に反するというかなんというか。
どこで困った?
URI::query_form()
ではまりました。
use URI; my $uri = URI->new('http://example.com/'); use utf8; $uri->query_form( bytes => "\x{a4}" ); print $uri, "\n"; #=> http://example.com/?bytes=%C2%A4
use utf8
してたから "\x{a4}"
が U+00A4
としてみなされたんだろうなぁ((ほんとはここ誤解です。utf8::is_utf8("\x{a4}")
しても off です。)),しかたねえなぁと思って別の API をたたいてみたら,
$uri->query( "bytes=\x{a4}" ); print $uri, "\n"; #=> http://example.com/?bytes=%A4
きちんとエンコードできるでないか。なぜだなぜだ。
と思って調べたら,先ほどの現象をみつけたわけです。
先ほどと同じように
$uri->query_form( 'bytes' => "\x{a4}" ); print $uri, "\n"; #=> http://example.com/?bytes=%A4
とすれば回避できます。
URI::query_form()
の
push(@query, "$key=$val");
というところで,たとえ $val
が UTF8 flag off だったとしても $key
が(=>
の左辺などで)UTF8 flag on だった場合,全体が UTF8 flag on に upgrade してしまうんですね。んでその場合,UTF8::Escape::escape_char()
が UTF8 flag on 文字列を UTF8 octets に convert してしまうので上記のような変なエスケープになっているわけです。