@kyanny's blog

My thoughts, my life. Views/opinions are my own.

CGI.pm の textfield(), popup_menu(), checkbox_group() などを使ったときに HTML が文字化けするときの対処方法

CGI.pm の textfield(), popup_menu(), checkbox_group() などを使ってフォームの部品の HTML を出力させたとき、テキストフィールドのデフォルト文字列やセレクトボックスのラベル部分などが一部だけ文字化けしてしまうことがある。

これは、 CGI::escapeHTML() のエスケープ処理が原因 (textfield() などは内部で escapeHTML() を呼んでいるため、影響を受ける)。

いろいろなパターンを検証したわけではないが、

  • ソースコードの文字エンコーディングは UTF-8
  • 出力する文字エンコーディングも UTF-8

のときに、文字化けがおこった。

これを回避するためには、

  • CGI::autoEscape(0) とする
  • CGI::charset("utf-8") とする

いずれかをやってから textfield() などを呼ぶと、文字化けしなくなる。以下検証用コード。

#!perl
use strict;
use CGI;
use Test::More;
plan tests => 9;


diag("普通に使う");
{
    # textfield のデフォルト値
    isnt(
        CGI::textfield( -name => "job", -value => "ニート" ),
        q{<input type="text" name="job" value="ニート" />},
        "textfield 文字化け",
    );

    # popup_menu (selectbox) のラベル
    isnt(
        CGI::popup_menu( -name => "job", -values => [ qw(1 2) ], -labels => { 1 => "無職", 2 => "ニート" } ),
        q{<select name="job" >
<option value="1">無職</option>
<option value="2">ニート</option>
</select>},
        "popup_menu 文字化け",
    );

    # checkbox_group のラベル
    isnt(
        CGI::checkbox_group( -name => "job", -values => [qw(1)], -labels => { 1 => "ニート" }),
        q{<label><input type="checkbox" name="job" value="1" />ニート</label>},
        "checkbox_group 文字化け",
    );
}


diag(q{CGI::charset("utf-8") をセットする"});
{
    CGI::charset("utf-8");
    # textfield のデフォルト値
    is(
        CGI::textfield( -name => "job", -value => "ニート" ),
        q{<input type="text" name="job" value="ニート" />},
        "textfield 化けない",
    );

    # popup_menu (selectbox) のラベル
    is(
        CGI::popup_menu( -name => "job", -values => [ qw(1 2) ], -labels => { 1 => "無職", 2 => "ニート" } ),
        q{<select name="job" >
<option value="1">無職</option>
<option value="2">ニート</option>
</select>},
        "popup_menu 化けない",
    );

    # checkbox_group のラベル
    is(
        CGI::checkbox_group( -name => "job", -values => [qw(1)], -labels => { 1 => "ニート" }),
        q{<label><input type="checkbox" name="job" value="1" />ニート</label>},
        "checkbox_group 化けない",
    );
}


diag("CGI::autoEscape(0) をセットする");
# これは $cgi->autoEscape(0) としてから $cgi->textfield() などを呼んでも良い
{
    CGI::autoEscape(0);
    is(
        CGI::textfield( -name => "job", -value => "ニート" ),
        q{<input type="text" name="job" value="ニート" />},
        "textfield 化けない",
    );

    # popup_menu (selectbox) のラベル
    is(
        CGI::popup_menu( -name => "job", -values => [ qw(1 2) ], -labels => { 1 => "無職", 2 => "ニート" } ),
        q{<select name="job" >
<option value="1">無職</option>
<option value="2">ニート</option>
</select>},
        "popup_menu 化けない",
    );

    # checkbox_group のラベル
    is(
        CGI::checkbox_group( -name => "job", -values => [qw(1)], -labels => { 1 => "ニート" }),
        q{<label><input type="checkbox" name="job" value="1" />ニート</label>},
        "checkbox_group 化けない",
    );
}

escapeHTML() を読むと、文字コードが "ISO-8859-1" か "WINDOWS-1252" だった場合に、 "\x8b" と "\x9b" という二文字をエスケープしてそれぞれ "‹" と "›" にしている。コメントによると、 "# Handle bug in some browsers with Latin charsets" だそうで、ブラウザのバグに対処するためのコードらしい。

この "\x8b" "\x9b" という二文字は、 Terminal.app で文字エンコーディングを「欧米 (Windows Latin 1)」にしたときに以下のようにすると表示される文字(UTF-8 だと ? になってしまう)。 wordpress で作ってあるブログのタイトル部分によく使われている、 < と > の小さいっぽい文字のようだ。

kyanny$ perl -e 'print q{\x8b}, " => ",  "\x8b", "\n"'
\x8b => &#8249;
kyanny$ perl -e 'print q{\x9b}, " => ",  "\x9b", "\n"'
\x9b => &#8250;