Perl で UTF-8

オライリーの「CGIプログラミング 第2版」の p.172 に以下のような例が載っている(Embperl の利用例)のだけど、なぜか HTML のテーブル部分の日本語が文字化けして困った。

<html>
  <head>

    <title>Table Sample</title>
  </head>
  <body>

    <div align="center">
      <h1>$row, $col の実験</h1>

      [- @sports = ( [ "ウインドサーフィン", "夏", "海・湖" ],
      [ "スキー", "冬", "山" ],
      [ "バイク", "通年", "峠" ],
      [ "キャンプ", "通年", "野原" ] ); -]

      <table border="1" cellpadding="4" width="400">
          <tr>
            <th>スポーツ</th>
            <th>シーズン</th>
            <th>スポット</th>
          </tr>
          <tr>
            <td>[+ decode('UTF-8', $sports[$row][$col]) +]</td>
          </tr>
      </table>
    </div>
  </body>
</html>


ググっていろいろと試行錯誤したところ、どうやら Encode::decode() を使って、Perl の内部形式(UTF-8)に文字列を変換すれば、文字化けせずに表示できることが分かった。以下にコードを載せておく。

[-
use utf8;
use Encode;
-]
<html>
  <head>

    <title>Table Sample</title>
  </head>
  <body>

    <div align="center">
      <h1>$row, $col の実験</h1>

      [- @sports = ( [ "ウインドサーフィン", "夏", "海・湖" ],
      [ "スキー", "冬", "山" ],
      [ "バイク", "通年", "峠" ],
      [ "キャンプ", "通年", "野原" ] ); -]

      <table border="1" cellpadding="4" width="400">
          <tr>
            <th>スポーツ</th>
            <th>シーズン</th>
            <th>スポット</th>
          </tr>
          <tr>
            <td>[+ decode('UTF-8', $sports[$row][$col]) +]</td>
          </tr>
      </table>
    </div>
  </body>
</html>

なぜこうするとうまくいくのか?

自分が参考にしたの第8項と「perldoc Encode」によると、

8. Embperl and the utf-8 flag
Strings stored to %udat keep their utf-8 flag, so you don't need to
worry about that.
%fdat is different, however (Gerald has it on the post-2.0 TODO list).
Here also applies what I said in 7. %fdat can be converted by doing

foreach my $k (keys %fdat) {
  utf8::decode($fdat{$k});
}
perldoc Encode(CAVEAT に書かれていることが重要)
$string = decode(ENCODING, $octets [, CHECK])
  Decodes a sequence of octets assumed to be in ENCODING into Perl’s internal form and returns the resulting string.  As in encode(), ENCODING can be either
   a canonical name or an alias. For encoding names and aliases, see "Defining Aliases".  For CHECK, see "Handling Malformed Data".

  For example, to convert ISO-8859-1 data to a string in Perl’s internal format:

     $string = decode("iso-8859-1", $octets);

   CAVEAT: When you run "$string = decode("utf8", $octets)", then $string may not be equal to 
   $octets.  Though they both contain the same data, the utf8 flag for $string is on unless
   $octets entirely consists of ASCII data (or EBCDIC on EBCDIC machines).  See "The UTF-8 flag" below.
perldoc Encode(同じくCAVEAT に書かれていることが重要)
CAVEAT: The following operations look the same but are not quite so;

  from_to($data, "iso-8859-1", "utf8"); #1
  $data = decode("iso-8859-1", $data);  #2

  Both #1 and #2 make $data consist of a completely valid UTF-8 string but only #2 turns utf8 flag on.  #1 is equivalent to

  $data = encode("utf8", decode("iso-8859-1", $data));

とのことなので、恐らく、「テーブル内のコードで特別な変数名 $row, $col, $cnt を使うと、動的にテーブルの構築を行う」という Embperl の仕組みを使うと、utf-8 flag が OFF になってしまうから、utf-8 flag を ON にする目的のためだけに、(変換しなくてももともと UTF-8 の $string を) Encode::decode('UTF-8', $string) としなければならないんだと思う。


perldoc Encode の「The UTF-8 flag」っていう章を読んでみると、UTF-8 辺りの話は結構複雑みたい(utf8 と UTF-8 の扱いが違ったり…)。とりあえず問題は解決したので、また困ったらこのエントリを読み返そう。


いろいろ書いたけど、正直、よく理解してないので、もし間違ってたら突っ込んでください。